commit a1c6197b1d5774f2ef7debebff7a19678805bd51
parent a949dda7efd1f6c8b7d04ed4d8380248c5b20c00
Author: Zack Newman <zack@philomathiclife.com>
Date: Thu, 22 Aug 2024 17:17:22 -0600
integrate relevant changes from upstream
Diffstat:
37 files changed, 18807 insertions(+), 20980 deletions(-)
diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs
@@ -1,11 +1,12 @@
use crate::{
- api::{EmptyResult, JsonResult, JsonUpcase, NumberOrString, PasswordOrOtpData},
+ api::{EmptyResult, JsonResult, PasswordOrOtpData},
auth::{decode_delete, ClientHeaders, Headers},
db::{
- models::{Cipher, Device, Folder, User, UserKdfType},
+ models::{Cipher, Device, Folder, User, UserKdfType, UserOrganization},
DbConn,
},
error::Error,
+ util::NumberOrString,
};
use rocket::serde::json::Json;
use rocket::{
@@ -22,7 +23,6 @@ pub fn routes() -> Vec<rocket::Route> {
get_auth_request_response,
get_auth_requests,
get_known_device,
- get_known_device_from_path,
get_public_keys,
password_hint,
post_auth_request,
@@ -55,29 +55,40 @@ pub fn routes() -> Vec<rocket::Route> {
]
}
-#[derive(Deserialize)]
-#[allow(dead_code, non_snake_case)]
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
pub struct RegisterData {
- Email: String,
- Kdf: Option<i32>,
- KdfIterations: Option<i32>,
- KdfMemory: Option<i32>,
- KdfParallelism: Option<i32>,
- Key: String,
- Keys: Option<KeysData>,
- MasterPasswordHash: String,
- MasterPasswordHint: Option<String>,
- Name: Option<String>,
- Token: Option<String>,
#[allow(dead_code)]
- OrganizationUserId: Option<String>,
+ email: String,
+ #[allow(dead_code)]
+ kdf: Option<i32>,
+ #[allow(dead_code)]
+ kdf_iterations: Option<i32>,
+ #[allow(dead_code)]
+ kdf_memory: Option<i32>,
+ #[allow(dead_code)]
+ kdf_parallelism: Option<i32>,
+ #[allow(dead_code)]
+ key: String,
+ #[allow(dead_code)]
+ keys: Option<KeysData>,
+ #[allow(dead_code)]
+ master_password_hash: String,
+ #[allow(dead_code)]
+ master_password_hint: Option<String>,
+ #[allow(dead_code)]
+ name: Option<String>,
+ #[allow(dead_code)]
+ token: Option<String>,
+ #[allow(dead_code)]
+ organization_user_id: Option<String>,
}
-#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
struct KeysData {
- EncryptedPrivateKey: String,
- PublicKey: String,
+ encrypted_private_key: String,
+ public_key: String,
}
/// Trims whitespace from password hints, and converts blank password hints to `None`.
@@ -97,7 +108,7 @@ fn enforce_password_hint_setting(password_hint: &Option<String>) -> EmptyResult
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/accounts/register", data = "<data>")]
-fn register(data: JsonUpcase<RegisterData>) -> Error {
+fn register(data: Json<RegisterData>) -> Error {
const MSG: &str = "Registration is permanently disabled.";
Error::new(MSG, MSG)
}
@@ -107,45 +118,45 @@ async fn profile(headers: Headers, conn: DbConn) -> Json<Value> {
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct ProfileData {
// Culture: String, // Ignored, always use en-US
// MasterPasswordHint: Option<String>, // Ignored, has been moved to ChangePassData
- Name: String,
+ name: String,
}
#[put("/accounts/profile", data = "<data>")]
-async fn put_profile(data: JsonUpcase<ProfileData>, headers: Headers, conn: DbConn) -> JsonResult {
+async fn put_profile(data: Json<ProfileData>, headers: Headers, conn: DbConn) -> JsonResult {
post_profile(data, headers, conn).await
}
#[post("/accounts/profile", data = "<data>")]
-async fn post_profile(data: JsonUpcase<ProfileData>, headers: Headers, conn: DbConn) -> JsonResult {
- let prof_data: ProfileData = data.into_inner().data;
+async fn post_profile(data: Json<ProfileData>, headers: Headers, conn: DbConn) -> JsonResult {
+ let prof_data: ProfileData = data.into_inner();
// Check if the length of the username exceeds 50 characters (Same is Upstream Bitwarden)
// This also prevents issues with very long usernames causing to large JWT's. See #2419
- if prof_data.Name.len() > 50 {
+ if prof_data.name.len() > 50 {
err!("The field Name must be a string with a maximum length of 50.");
}
let mut user = headers.user;
- user.name = prof_data.Name;
+ user.name = prof_data.name;
user.save(&conn).await?;
Ok(Json(user.to_json(&conn).await))
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct AvatarData {
- AvatarColor: Option<String>,
+ avatar_color: Option<String>,
}
#[put("/accounts/avatar", data = "<data>")]
-async fn put_avatar(data: JsonUpcase<AvatarData>, headers: Headers, conn: DbConn) -> JsonResult {
- let av_data: AvatarData = data.into_inner().data;
+async fn put_avatar(data: Json<AvatarData>, headers: Headers, conn: DbConn) -> JsonResult {
+ let av_data: AvatarData = data.into_inner();
// It looks like it only supports the 6 hex color format.
// If you try to add the short value it will not show that color.
// Check and force 7 chars, including the #.
- if let Some(ref color) = av_data.AvatarColor {
+ if let Some(ref color) = av_data.avatar_color {
if color.len() != 7 {
err!(
"The field AvatarColor must be a HTML/Hex color code with a length of 7 characters"
@@ -153,7 +164,7 @@ async fn put_avatar(data: JsonUpcase<AvatarData>, headers: Headers, conn: DbConn
}
}
let mut user = headers.user;
- user.avatar_color = av_data.AvatarColor;
+ user.avatar_color = av_data.avatar_color;
user.save(&conn).await?;
Ok(Json(user.to_json(&conn).await))
}
@@ -164,51 +175,47 @@ async fn get_public_keys(uuid: &str, _headers: Headers, conn: DbConn) -> JsonRes
err!("User doesn't exist")
};
Ok(Json(json!({
- "UserId": user.uuid,
- "PublicKey": user.public_key,
- "Object":"userKey"
+ "userId": user.uuid,
+ "publicKey": user.public_key,
+ "object":"userKey"
})))
}
#[post("/accounts/keys", data = "<data>")]
-async fn post_keys(data: JsonUpcase<KeysData>, headers: Headers, conn: DbConn) -> JsonResult {
- let key_data: KeysData = data.into_inner().data;
+async fn post_keys(data: Json<KeysData>, headers: Headers, conn: DbConn) -> JsonResult {
+ let key_data: KeysData = data.into_inner();
let mut user = headers.user;
- user.private_key = Some(key_data.EncryptedPrivateKey);
- user.public_key = Some(key_data.PublicKey);
+ user.private_key = Some(key_data.encrypted_private_key);
+ user.public_key = Some(key_data.public_key);
user.save(&conn).await?;
Ok(Json(json!({
- "PrivateKey": user.private_key,
- "PublicKey": user.public_key,
- "Object":"keys"
+ "privateKey": user.private_key,
+ "publicKey": user.public_key,
+ "object":"keys"
})))
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct ChangePassData {
- MasterPasswordHash: String,
- NewMasterPasswordHash: String,
- MasterPasswordHint: Option<String>,
- Key: String,
+ master_password_hash: String,
+ new_master_password_hash: String,
+ master_password_hint: Option<String>,
+ key: String,
}
#[post("/accounts/password", data = "<data>")]
-async fn post_password(
- data: JsonUpcase<ChangePassData>,
- headers: Headers,
- conn: DbConn,
-) -> EmptyResult {
- let pass_data: ChangePassData = data.into_inner().data;
+async fn post_password(data: Json<ChangePassData>, headers: Headers, conn: DbConn) -> EmptyResult {
+ let pass_data: ChangePassData = data.into_inner();
let mut user = headers.user;
- if !user.check_valid_password(&pass_data.MasterPasswordHash) {
+ if !user.check_valid_password(&pass_data.master_password_hash) {
err!("Invalid password")
}
- user.password_hint = clean_password_hint(&pass_data.MasterPasswordHint);
+ user.password_hint = clean_password_hint(&pass_data.master_password_hint);
enforce_password_hint_setting(&user.password_hint)?;
user.set_password(
- &pass_data.NewMasterPasswordHash,
- Some(pass_data.Key),
+ &pass_data.new_master_password_hash,
+ Some(pass_data.key),
true,
Some(vec![
String::from("post_rotatekey"),
@@ -220,44 +227,44 @@ async fn post_password(
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct ChangeKdfData {
- Kdf: i32,
- KdfIterations: u32,
- KdfMemory: Option<u32>,
- KdfParallelism: Option<u32>,
- MasterPasswordHash: String,
- NewMasterPasswordHash: String,
- Key: String,
+ kdf: i32,
+ kdf_iterations: u32,
+ kdf_memory: Option<u32>,
+ kdf_parallelism: Option<u32>,
+ master_password_hash: String,
+ new_master_password_hash: String,
+ key: String,
}
#[post("/accounts/kdf", data = "<data>")]
-async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, conn: DbConn) -> EmptyResult {
- let kdf_data: ChangeKdfData = data.into_inner().data;
+async fn post_kdf(data: Json<ChangeKdfData>, headers: Headers, conn: DbConn) -> EmptyResult {
+ let kdf_data: ChangeKdfData = data.into_inner();
let mut user = headers.user;
- if !user.check_valid_password(&kdf_data.MasterPasswordHash) {
+ if !user.check_valid_password(&kdf_data.master_password_hash) {
err!("Invalid password")
}
- if kdf_data.Kdf == i32::from(UserKdfType::Pbkdf2) && kdf_data.KdfIterations < 100_000u32 {
+ if kdf_data.kdf == i32::from(UserKdfType::Pbkdf2) && kdf_data.kdf_iterations < 100_000u32 {
err!("PBKDF2 KDF iterations must be at least 100000.")
}
- if kdf_data.Kdf == i32::from(UserKdfType::Argon2id) {
- if kdf_data.KdfIterations < 1u32 {
+ if kdf_data.kdf == i32::from(UserKdfType::Argon2id) {
+ if kdf_data.kdf_iterations < 1u32 {
err!("Argon2 KDF iterations must be at least 1.")
}
- if let Some(m) = kdf_data.KdfMemory {
+ if let Some(m) = kdf_data.kdf_memory {
if !(15u32..=1024u32).contains(&m) {
err!("Argon2 memory must be between 15 MB and 1024 MB.")
}
- user.set_client_kdf_memory(kdf_data.KdfMemory);
+ user.set_client_kdf_memory(kdf_data.kdf_memory);
} else {
err!("Argon2 memory parameter is required.")
}
- if let Some(p) = kdf_data.KdfParallelism {
+ if let Some(p) = kdf_data.kdf_parallelism {
if !(1u32..=16u32).contains(&p) {
err!("Argon2 parallelism must be between 1 and 16.")
}
- user.set_client_kdf_parallelism(kdf_data.KdfParallelism);
+ user.set_client_kdf_parallelism(kdf_data.kdf_parallelism);
} else {
err!("Argon2 parallelism parameter is required.")
}
@@ -265,11 +272,11 @@ async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, conn: DbCon
user.set_client_kdf_memory(None);
user.set_client_kdf_parallelism(None);
}
- user.set_client_kdf_iter(kdf_data.KdfIterations);
- user.client_kdf_type = kdf_data.Kdf;
+ user.set_client_kdf_iter(kdf_data.kdf_iterations);
+ user.client_kdf_type = kdf_data.kdf;
user.set_password(
- &kdf_data.NewMasterPasswordHash,
- Some(kdf_data.Key),
+ &kdf_data.new_master_password_hash,
+ Some(kdf_data.key),
true,
None,
);
@@ -277,30 +284,38 @@ async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, conn: DbCon
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct UpdateFolderData {
- Id: String,
- Name: String,
+ id: Option<String>,
+ name: String,
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct UpdateResetPasswordData {
+ organization_id: String,
+ reset_password_key: String,
}
use super::ciphers::CipherData;
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct KeyData {
- Ciphers: Vec<CipherData>,
- Folders: Vec<UpdateFolderData>,
- Key: String,
- PrivateKey: String,
- MasterPasswordHash: String,
+ ciphers: Vec<CipherData>,
+ folders: Vec<UpdateFolderData>,
+ reset_password_keys: Vec<UpdateResetPasswordData>,
+ key: String,
+ master_password_hash: String,
+ private_key: String,
}
#[post("/accounts/key", data = "<data>")]
-async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn) -> EmptyResult {
- let key_data: KeyData = data.into_inner().data;
+async fn post_rotatekey(data: Json<KeyData>, headers: Headers, conn: DbConn) -> EmptyResult {
+ let key_data: KeyData = data.into_inner();
if !headers
.user
- .check_valid_password(&key_data.MasterPasswordHash)
+ .check_valid_password(&key_data.master_password_hash)
{
err!("Invalid password")
}
@@ -308,49 +323,63 @@ async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbCon
// Bitwarden does not process the import if there is one item invalid.
// Since we check for the size of the encrypted note length, we need to do that here to pre-validate it.
// TODO: See if we can optimize the whole cipher adding/importing and prevent duplicate code and checks.
- Cipher::validate_notes(&key_data.Ciphers)?;
+ Cipher::validate_notes(&key_data.ciphers)?;
let user_uuid = &headers.user.uuid;
// Update folder data
- for folder_data in key_data.Folders {
- let Some(mut saved_folder) = Folder::find_by_uuid(&folder_data.Id, &conn).await else {
- err!("Folder doesn't exist")
- };
- if &saved_folder.user_uuid != user_uuid {
- err!("The folder is not owned by the user")
+ for folder_data in key_data.folders {
+ if let Some(folder_id) = folder_data.id {
+ let Some(mut saved_folder) = Folder::find_by_uuid(&folder_id, &conn).await else {
+ err!("Folder doesn't exist")
+ };
+ if &saved_folder.user_uuid != user_uuid {
+ err!("The folder is not owned by the user")
+ }
+
+ saved_folder.name = folder_data.name;
+ saved_folder.save(&conn).await?;
}
- saved_folder.name = folder_data.Name;
- saved_folder.save(&conn).await?;
}
- // Update cipher data
- use super::ciphers::update_cipher_from_data;
- for cipher_data in key_data.Ciphers {
- let Some(mut saved_cipher) =
- Cipher::find_by_uuid(cipher_data.Id.as_ref().unwrap(), &conn).await
+ for reset_password_data in key_data.reset_password_keys {
+ let Some(mut user_org) = UserOrganization::find_by_user_and_org(
+ user_uuid,
+ &reset_password_data.organization_id,
+ &conn,
+ )
+ .await
else {
- err!("Cipher doesn't exist")
+ err!("Reset password doesn't exist")
};
- if saved_cipher.user_uuid.as_ref().unwrap() != user_uuid {
- err!("The cipher is not owned by the user")
+ user_org.reset_password_key = Some(reset_password_data.reset_password_key);
+ user_org.save(&conn).await?;
+ }
+ // Update cipher data
+ use super::ciphers::update_cipher_from_data;
+ for cipher_data in key_data.ciphers {
+ if cipher_data.organization_id.is_none() {
+ let Some(mut saved_cipher) =
+ Cipher::find_by_uuid(cipher_data.id.as_ref().unwrap(), &conn).await
+ else {
+ err!("Cipher doesn't exist")
+ };
+ if saved_cipher.user_uuid.as_ref().unwrap() != user_uuid {
+ err!("The cipher is not owned by the user")
+ }
+ update_cipher_from_data(&mut saved_cipher, cipher_data, &headers, None, &conn, true)
+ .await?;
}
- update_cipher_from_data(&mut saved_cipher, cipher_data, &headers, false, &conn, true)
- .await?;
}
// Update user data
let mut user = headers.user;
- user.akey = key_data.Key;
- user.private_key = Some(key_data.PrivateKey);
+ user.akey = key_data.key;
+ user.private_key = Some(key_data.private_key);
user.reset_security_stamp();
user.save(&conn).await
}
#[post("/accounts/security-stamp", data = "<data>")]
-async fn post_sstamp(
- data: JsonUpcase<PasswordOrOtpData>,
- headers: Headers,
- conn: DbConn,
-) -> EmptyResult {
- let otp_data: PasswordOrOtpData = data.into_inner().data;
+async fn post_sstamp(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> EmptyResult {
+ let otp_data: PasswordOrOtpData = data.into_inner();
let mut user = headers.user;
otp_data.validate(&user)?;
Device::delete_all_by_user(&user.uuid, &conn).await?;
@@ -359,32 +388,39 @@ async fn post_sstamp(
}
#[derive(Deserialize)]
-#[allow(dead_code, non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct EmailTokenData {
- MasterPasswordHash: String,
- NewEmail: String,
+ #[allow(dead_code)]
+ master_password_hash: String,
+ #[allow(dead_code)]
+ new_email: String,
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/accounts/email-token", data = "<data>")]
-fn post_email_token(data: JsonUpcase<EmailTokenData>, _headers: Headers) -> Error {
+fn post_email_token(data: Json<EmailTokenData>, _headers: Headers) -> Error {
const MSG: &str = "E-mail change is not allowed.";
Error::new(MSG, MSG)
}
#[derive(Deserialize)]
-#[allow(dead_code, non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct ChangeEmailData {
- MasterPasswordHash: String,
- NewEmail: String,
- Key: String,
- NewMasterPasswordHash: String,
- Token: NumberOrString,
+ #[allow(dead_code)]
+ master_password_hash: String,
+ #[allow(dead_code)]
+ new_email: String,
+ #[allow(dead_code)]
+ key: String,
+ #[allow(dead_code)]
+ new_master_password_hash: String,
+ #[allow(dead_code)]
+ token: NumberOrString,
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/accounts/email", data = "<data>")]
-fn post_email(data: JsonUpcase<ChangeEmailData>, _headers: Headers) -> Error {
+fn post_email(data: Json<ChangeEmailData>, _headers: Headers) -> Error {
const MSG: &str = "E-mail change is not allowed.";
Error::new(MSG, MSG)
}
@@ -397,49 +433,52 @@ fn post_verify_email(_headers: Headers) -> Error {
}
#[derive(Deserialize)]
-#[allow(non_snake_case, dead_code)]
+#[serde(rename_all = "camelCase")]
struct VerifyEmailTokenData {
- UserId: String,
- Token: String,
+ #[allow(dead_code)]
+ user_id: String,
+ #[allow(dead_code)]
+ token: String,
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/accounts/verify-email-token", data = "<data>")]
-fn post_verify_email_token(data: JsonUpcase<VerifyEmailTokenData>) -> Error {
+fn post_verify_email_token(data: Json<VerifyEmailTokenData>) -> Error {
const MSG: &str = "E-mail is disabled.";
Error::new(MSG, MSG)
}
#[derive(Deserialize)]
-#[allow(dead_code, non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct DeleteRecoverData {
- Email: String,
+ #[allow(dead_code)]
+ email: String,
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/accounts/delete-recover", data = "<data>")]
-fn post_delete_recover(data: JsonUpcase<DeleteRecoverData>) -> Error {
+fn post_delete_recover(data: Json<DeleteRecoverData>) -> Error {
const MSG: &str = "Account deletion is disabled with at this endpoint.";
Error::new(MSG, MSG)
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct DeleteRecoverTokenData {
- UserId: String,
- Token: String,
+ user_id: String,
+ token: String,
}
#[post("/accounts/delete-recover-token", data = "<data>")]
async fn post_delete_recover_token(
- data: JsonUpcase<DeleteRecoverTokenData>,
+ data: Json<DeleteRecoverTokenData>,
conn: DbConn,
) -> EmptyResult {
- let token_data: DeleteRecoverTokenData = data.into_inner().data;
- let Some(user) = User::find_by_uuid(&token_data.UserId, &conn).await else {
+ let token_data: DeleteRecoverTokenData = data.into_inner();
+ let Some(user) = User::find_by_uuid(&token_data.user_id, &conn).await else {
err!("User doesn't exist")
};
- let Ok(claims) = decode_delete(&token_data.Token) else {
+ let Ok(claims) = decode_delete(&token_data.token) else {
err!("Invalid claim")
};
if claims.sub != user.uuid {
@@ -450,7 +489,7 @@ async fn post_delete_recover_token(
#[post("/accounts/delete", data = "<data>")]
async fn post_delete_account(
- data: JsonUpcase<PasswordOrOtpData>,
+ data: Json<PasswordOrOtpData>,
headers: Headers,
conn: DbConn,
) -> EmptyResult {
@@ -459,11 +498,11 @@ async fn post_delete_account(
#[delete("/accounts", data = "<data>")]
async fn delete_account(
- data: JsonUpcase<PasswordOrOtpData>,
+ data: Json<PasswordOrOtpData>,
headers: Headers,
conn: DbConn,
) -> EmptyResult {
- let otp_data: PasswordOrOtpData = data.into_inner().data;
+ let otp_data: PasswordOrOtpData = data.into_inner();
let user = headers.user;
otp_data.validate(&user)?;
user.delete(&conn).await
@@ -475,33 +514,34 @@ fn revision_date(headers: Headers) -> Json<Value> {
}
#[derive(Deserialize)]
-#[allow(dead_code, non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct PasswordHintData {
- Email: String,
+ #[allow(dead_code)]
+ email: String,
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/accounts/password-hint", data = "<data>")]
-fn password_hint(data: JsonUpcase<PasswordHintData>) -> Error {
+fn password_hint(data: Json<PasswordHintData>) -> Error {
const MSG: &str = "Password hints are disabled.";
Error::new(MSG, MSG)
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
pub struct PreloginData {
- Email: String,
+ email: String,
}
#[post("/accounts/prelogin", data = "<data>")]
-async fn prelogin(data: JsonUpcase<PreloginData>, conn: DbConn) -> Json<Value> {
+async fn prelogin(data: Json<PreloginData>, conn: DbConn) -> Json<Value> {
_prelogin(data, conn).await
}
-pub async fn _prelogin(data: JsonUpcase<PreloginData>, conn: DbConn) -> Json<Value> {
- let login_data: PreloginData = data.into_inner().data;
+pub async fn _prelogin(data: Json<PreloginData>, conn: DbConn) -> Json<Value> {
+ let login_data: PreloginData = data.into_inner();
let (kdf_type, kdf_iter, kdf_mem, kdf_para) =
- match User::find_by_mail(&login_data.Email, &conn).await {
+ match User::find_by_mail(&login_data.email, &conn).await {
Some(user) => (
user.client_kdf_type,
user.client_kdf_iter(),
@@ -516,26 +556,26 @@ pub async fn _prelogin(data: JsonUpcase<PreloginData>, conn: DbConn) -> Json<Val
),
};
let result = json!({
- "Kdf": kdf_type,
- "KdfIterations": kdf_iter,
- "KdfMemory": kdf_mem,
- "KdfParallelism": kdf_para,
+ "kdf": kdf_type,
+ "kdfIterations": kdf_iter,
+ "kdfMemory": kdf_mem,
+ "kdfParallelism": kdf_para,
});
Json(result)
}
// https://github.com/bitwarden/server/blob/master/src/Api/Models/Request/Accounts/SecretVerificationRequestModel.cs
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct SecretVerificationRequest {
- MasterPasswordHash: String,
+ master_password_hash: String,
}
#[post("/accounts/verify-password", data = "<data>")]
-fn verify_password(data: JsonUpcase<SecretVerificationRequest>, headers: Headers) -> EmptyResult {
- let req: SecretVerificationRequest = data.into_inner().data;
+fn verify_password(data: Json<SecretVerificationRequest>, headers: Headers) -> EmptyResult {
+ let req: SecretVerificationRequest = data.into_inner();
let user = headers.user;
- if !user.check_valid_password(&req.MasterPasswordHash) {
+ if !user.check_valid_password(&req.master_password_hash) {
err!("Invalid password")
}
Ok(())
@@ -544,34 +584,27 @@ fn verify_password(data: JsonUpcase<SecretVerificationRequest>, headers: Headers
const API_DISABLED_MSG: &str = "API access is disabled.";
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/accounts/api-key", data = "<data>")]
-fn api_key(data: JsonUpcase<PasswordOrOtpData>, _headers: Headers) -> Error {
+fn api_key(data: Json<PasswordOrOtpData>, _headers: Headers) -> Error {
Error::new(API_DISABLED_MSG, API_DISABLED_MSG)
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/accounts/rotate-api-key", data = "<data>")]
-fn rotate_api_key(data: JsonUpcase<PasswordOrOtpData>, _headers: Headers) -> Error {
+fn rotate_api_key(data: Json<PasswordOrOtpData>, _headers: Headers) -> Error {
Error::new(API_DISABLED_MSG, API_DISABLED_MSG)
}
-// This variant is deprecated: https://github.com/bitwarden/server/pull/2682
-#[get("/devices/knowndevice/<email>/<uuid>")]
-async fn get_known_device_from_path(email: &str, uuid: &str, conn: DbConn) -> JsonResult {
- // This endpoint doesn't have auth header
+#[get("/devices/knowndevice")]
+async fn get_known_device(device: KnownDevice, conn: DbConn) -> JsonResult {
let mut result = false;
- if let Some(user) = User::find_by_mail(email, &conn).await {
- result = Device::find_by_uuid_and_user(uuid, &user.uuid, &conn)
+ if let Some(user) = User::find_by_mail(&device.email, &conn).await {
+ result = Device::find_by_uuid_and_user(&device.uuid, &user.uuid, &conn)
.await
.is_some();
}
Ok(Json(json!(result)))
}
-#[get("/devices/knowndevice")]
-async fn get_known_device(device: KnownDevice, conn: DbConn) -> JsonResult {
- get_known_device_from_path(&device.email, &device.uuid, conn).await
-}
-
struct KnownDevice {
email: String,
uuid: String,
@@ -612,15 +645,18 @@ impl<'r> FromRequest<'r> for KnownDevice {
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
-struct PushToken;
+#[serde(rename_all = "camelCase")]
+struct PushToken {
+ #[allow(dead_code)]
+ push_token: String,
+}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/devices/identifier/<uuid>/token", data = "<data>")]
-fn post_device_token(uuid: &str, data: JsonUpcase<PushToken>, _headers: Headers) {}
+fn post_device_token(uuid: &str, data: Json<PushToken>, _headers: Headers) {}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[put("/devices/identifier/<uuid>/token", data = "<data>")]
-fn put_device_token(uuid: &str, data: JsonUpcase<PushToken>, _headers: Headers) {}
+fn put_device_token(uuid: &str, data: Json<PushToken>, _headers: Headers) {}
#[allow(unused_variables)]
#[put("/devices/identifier/<uuid>/clear-token")]
const fn put_clear_device_token(uuid: &str) {}
@@ -631,12 +667,16 @@ const fn put_clear_device_token(uuid: &str) {}
const fn post_clear_device_token(uuid: &str) {}
#[derive(Deserialize)]
-#[allow(non_snake_case, dead_code)]
+#[serde(rename_all = "camelCase")]
struct AuthRequestRequest {
- accessCode: String,
- deviceIdentifier: String,
+ #[allow(dead_code)]
+ access_code: String,
+ #[allow(dead_code)]
+ device_identifier: String,
+ #[allow(dead_code)]
email: String,
- publicKey: String,
+ #[allow(dead_code)]
+ public_key: String,
#[serde(alias = "type")]
_type: i32,
}
@@ -654,12 +694,16 @@ fn get_auth_request(uuid: &str) -> Error {
}
#[derive(Deserialize)]
-#[allow(non_snake_case, dead_code)]
+#[serde(rename_all = "camelCase")]
struct AuthResponseRequest {
- deviceIdentifier: String,
+ #[allow(dead_code)]
+ device_identifier: String,
+ #[allow(dead_code)]
key: String,
- masterPasswordHash: Option<String>,
- requestApproved: bool,
+ #[allow(dead_code)]
+ master_password_hash: Option<String>,
+ #[allow(dead_code)]
+ request_approved: bool,
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs
@@ -1,6 +1,6 @@
use super::folders::FolderData;
use crate::{
- api::{self, EmptyResult, JsonResult, JsonUpcase, PasswordOrOtpData},
+ api::{self, EmptyResult, JsonResult, PasswordOrOtpData},
auth::Headers,
db::{
models::{
@@ -10,12 +10,13 @@ use crate::{
DbConn,
},
error::Error,
+ util::NumberOrString,
};
use chrono::{NaiveDateTime, Utc};
-use rocket::fs::TempFile;
-use rocket::serde::json::Json;
use rocket::{
form::{Form, FromForm},
+ fs::TempFile,
+ serde::json::Json,
Route,
};
use serde_json::Value;
@@ -69,6 +70,7 @@ pub fn routes() -> Vec<Route> {
post_ciphers_admin,
post_ciphers_create,
post_ciphers_import,
+ post_collections2_update,
post_collections_admin,
post_collections_update,
put_cipher,
@@ -76,6 +78,7 @@ pub fn routes() -> Vec<Route> {
put_cipher_partial,
put_cipher_share,
put_cipher_share_selected,
+ put_collections2_update,
put_collections_admin,
put_collections_update,
restore_cipher_put,
@@ -136,15 +139,15 @@ async fn sync(data: SyncData, headers: Headers, conn: DbConn) -> Json<Value> {
api::core::_get_eq_domains(headers, true).into_inner()
};
Json(json!({
- "Profile": user_json,
- "Folders": folders_json,
- "Collections": collections_json,
- "Policies": policies_json,
- "Ciphers": ciphers_json,
- "Domains": domains_json,
- "Sends": Vec::<Value>::new(),
+ "profile": user_json,
+ "folders": folders_json,
+ "collections": collections_json,
+ "policies": policies_json,
+ "ciphers": ciphers_json,
+ "domains": domains_json,
+ "sends": Vec::<Value>::new(),
"unofficialServer": true,
- "Object": "sync"
+ "object": "sync"
}))
}
@@ -166,9 +169,9 @@ async fn get_ciphers(headers: Headers, conn: DbConn) -> Json<Value> {
);
}
Json(json!({
- "Data": ciphers_json,
- "Object": "list",
- "ContinuationToken": null
+ "data": ciphers_json,
+ "object": "list",
+ "continuationToken": null
}))
}
@@ -202,59 +205,63 @@ async fn get_cipher_details(uuid: &str, headers: Headers, conn: DbConn) -> JsonR
}
#[derive(Deserialize)]
-#[allow(dead_code, non_snake_case)]
+#[serde(rename_all = "camelCase")]
pub struct CipherData {
- // Id is optional as it is included only in bulk share
- pub Id: Option<String>,
- // Folder id is not included in import
- FolderId: Option<String>,
+ // id is optional as it is included only in bulk share
+ pub id: Option<String>,
+ // folder id is not included in import
+ folder_id: Option<String>,
// TODO: Some of these might appear all the time, no need for Option
- OrganizationId: Option<String>,
- Key: Option<String>,
- pub Type: i32,
- pub Name: String,
- pub Notes: Option<String>,
- Fields: Option<Value>,
+ #[serde(alias = "organizationID")]
+ pub organization_id: Option<String>,
+ key: Option<String>,
+ pub r#type: i32,
+ pub name: String,
+ pub notes: Option<String>,
+ fields: Option<Value>,
// Only one of these should exist, depending on type
- Login: Option<Value>,
- SecureNote: Option<Value>,
- Card: Option<Value>,
- Identity: Option<Value>,
- Favorite: Option<bool>,
- Reprompt: Option<i32>,
- PasswordHistory: Option<Value>,
+ login: Option<Value>,
+ secure_note: Option<Value>,
+ card: Option<Value>,
+ identity: Option<Value>,
+ favorite: Option<bool>,
+ reprompt: Option<i32>,
+ password_history: Option<Value>,
// These are used during key rotation
// 'Attachments' is unused, contains map of {id: filename}
- #[serde(rename = "Attachments")]
- _Attachments: Option<Value>,
- Attachments2: Option<HashMap<String, Attachments2Data>>,
+ #[allow(dead_code)]
+ attachments: Option<Value>,
+ #[allow(dead_code)]
+ attachments2: Option<HashMap<String, Attachments2Data>>,
// The revision datetime (in ISO 8601 format) of the client's local copy
// of the cipher. This is used to prevent a client from updating a cipher
// when it doesn't have the latest version, as that can result in data
// loss. It's not an error when no value is provided; this can happen
// when using older client versions, or if the operation doesn't involve
// updating an existing cipher.
- LastKnownRevisionDate: Option<String>,
+ last_known_revision_date: Option<String>,
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct PartialCipherData {
- FolderId: Option<String>,
- Favorite: bool,
+ folder_id: Option<String>,
+ favorite: bool,
}
#[derive(Deserialize)]
-#[allow(dead_code, non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct Attachments2Data {
- FileName: String,
- Key: String,
+ #[allow(dead_code)]
+ file_name: String,
+ #[allow(dead_code)]
+ key: String,
}
/// Called when an org admin clones an org cipher.
#[post("/ciphers/admin", data = "<data>")]
async fn post_ciphers_admin(
- data: JsonUpcase<ShareCipherData>,
+ data: Json<ShareCipherData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
@@ -266,21 +273,25 @@ async fn post_ciphers_admin(
/// `organizationId` is null.
#[post("/ciphers/create", data = "<data>")]
async fn post_ciphers_create(
- data: JsonUpcase<ShareCipherData>,
+ data: Json<ShareCipherData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
- let mut data: ShareCipherData = data.into_inner().data;
+ let mut data: ShareCipherData = data.into_inner();
// Check if there are one more more collections selected when this cipher is part of an organization.
// err if this is not the case before creating an empty cipher.
- if data.Cipher.OrganizationId.is_some() && data.CollectionIds.is_empty() {
+ if data.cipher.organization_id.is_some() && data.collection_ids.is_empty() {
err!("You must select at least one collection.");
}
+ // reverse sanity check to prevent corruptions
+ if !data.collection_ids.is_empty() && data.cipher.organization_id.is_none() {
+ err!("The client has not provided an organization id!");
+ }
// This check is usually only needed in update_cipher_from_data(), but we
// need it here as well to avoid creating an empty cipher in the call to
// cipher.save() below.
- enforce_personal_ownership_policy(Some(&data.Cipher), &headers, &conn).await?;
- let mut cipher = Cipher::new(data.Cipher.Type, data.Cipher.Name.clone());
+ enforce_personal_ownership_policy(Some(&data.cipher), &headers, &conn).await?;
+ let mut cipher = Cipher::new(data.cipher.r#type, data.cipher.name.clone());
cipher.user_uuid = Some(headers.user.uuid.clone());
cipher.save(&conn).await?;
// When cloning a cipher, the Bitwarden clients seem to set this field
@@ -289,21 +300,21 @@ async fn post_ciphers_create(
// the current time, so the stale data check will end up failing down the
// line. Since this function only creates new ciphers (whether by cloning
// or otherwise), we can just ignore this field entirely.
- data.Cipher.LastKnownRevisionDate = None;
+ data.cipher.last_known_revision_date = None;
share_cipher_by_uuid(&cipher.uuid, data, &headers, &conn).await
}
/// Called when creating a new user-owned cipher.
#[post("/ciphers", data = "<data>")]
-async fn post_ciphers(data: JsonUpcase<CipherData>, headers: Headers, conn: DbConn) -> JsonResult {
- let mut data: CipherData = data.into_inner().data;
+async fn post_ciphers(data: Json<CipherData>, headers: Headers, conn: DbConn) -> JsonResult {
+ let mut data: CipherData = data.into_inner();
// The web/browser clients set this field to null as expected, but the
// mobile clients seem to set the invalid value `0001-01-01T00:00:00`,
// which results in a warning message being logged. This field isn't
// needed when creating a new cipher, so just ignore it unconditionally.
- data.LastKnownRevisionDate = None;
- let mut cipher = Cipher::new(data.Type, data.Name.clone());
- update_cipher_from_data(&mut cipher, data, &headers, false, &conn, false).await?;
+ data.last_known_revision_date = None;
+ let mut cipher = Cipher::new(data.r#type, data.name.clone());
+ update_cipher_from_data(&mut cipher, data, &headers, None, &conn, false).await?;
Ok(Json(
cipher
.to_json(&headers.user.uuid, None, CipherSyncType::User, &conn)
@@ -323,7 +334,7 @@ async fn enforce_personal_ownership_policy(
headers: &Headers,
conn: &DbConn,
) -> EmptyResult {
- if data.is_none() || data.unwrap().OrganizationId.is_none() {
+ if data.is_none() || data.unwrap().organization_id.is_none() {
let user_uuid = &headers.user.uuid;
let policy_type = OrgPolicyType::PersonalOwnership;
if OrgPolicy::is_applicable_to_user(user_uuid, policy_type, None, conn).await {
@@ -337,7 +348,7 @@ pub async fn update_cipher_from_data(
cipher: &mut Cipher,
data: CipherData,
headers: &Headers,
- shared_to_collection: bool,
+ shared_to_collections: Option<Vec<String>>,
conn: &DbConn,
import_cipher: bool,
) -> EmptyResult {
@@ -345,7 +356,7 @@ pub async fn update_cipher_from_data(
// Check that the client isn't updating an existing cipher with stale data.
// And only perform this check when not importing ciphers, else the date/time check will fail.
if !import_cipher {
- if let Some(dt) = data.LastKnownRevisionDate {
+ if let Some(dt) = data.last_known_revision_date {
match NaiveDateTime::parse_from_str(&dt, "%+") {
// ISO 8601 format
Err(err) => warn!("Error parsing LastKnownRevisionDate '{}': {}", dt, err),
@@ -356,19 +367,19 @@ pub async fn update_cipher_from_data(
}
}
}
- if cipher.organization_uuid.is_some() && cipher.organization_uuid != data.OrganizationId {
+ if cipher.organization_uuid.is_some() && cipher.organization_uuid != data.organization_id {
err!("Organization mismatch. Please resync the client before updating the cipher")
}
- if let Some(ref note) = data.Notes {
+ if let Some(ref note) = data.notes {
if note.len() > 10_000 {
err!("The field Notes exceeds the maximum encrypted value length of 10000 characters.")
}
}
- if let Some(org_id) = data.OrganizationId {
+ if let Some(org_id) = data.organization_id {
match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, conn).await {
None => err!("You don't have permission to add item to organization"),
Some(org_user) => {
- if shared_to_collection
+ if shared_to_collections.is_some()
|| org_user.has_full_access()
|| cipher
.is_write_accessible_to_user(&headers.user.uuid, conn)
@@ -389,7 +400,7 @@ pub async fn update_cipher_from_data(
} else {
cipher.user_uuid = Some(headers.user.uuid.clone());
}
- if let Some(ref folder_id) = data.FolderId {
+ if let Some(ref folder_id) = data.folder_id {
match Folder::find_by_uuid(folder_id, conn).await {
Some(folder) => {
if folder.user_uuid != headers.user.uuid {
@@ -416,11 +427,11 @@ pub async fn update_cipher_from_data(
};
json_data
}
- let type_data_opt = match data.Type {
- 1i32 => data.Login,
- 2i32 => data.SecureNote,
- 3i32 => data.Card,
- 4i32 => data.Identity,
+ let type_data_opt = match data.r#type {
+ 1i32 => data.login,
+ 2i32 => data.secure_note,
+ 3i32 => data.card,
+ 4i32 => data.identity,
_ => err!("Invalid type"),
};
let type_data = match type_data_opt {
@@ -435,71 +446,81 @@ pub async fn update_cipher_from_data(
}
None => err!("Data missing"),
};
- cipher.key = data.Key;
- cipher.name = data.Name;
- cipher.notes = data.Notes;
- cipher.fields = data.Fields.map(|f| _clean_cipher_data(f).to_string());
+ cipher.key = data.key;
+ cipher.name = data.name;
+ cipher.notes = data.notes;
+ cipher.fields = data.fields.map(|f| _clean_cipher_data(f).to_string());
cipher.data = type_data.to_string();
- cipher.password_history = data.PasswordHistory.map(|f| f.to_string());
- cipher.reprompt = data.Reprompt;
+ cipher.password_history = data.password_history.map(|f| f.to_string());
+ cipher.reprompt = data.reprompt;
cipher.save(conn).await?;
cipher
- .move_to_folder(data.FolderId, &headers.user.uuid, conn)
+ .move_to_folder(data.folder_id, &headers.user.uuid, conn)
.await?;
cipher
- .set_favorite(data.Favorite, &headers.user.uuid, conn)
+ .set_favorite(data.favorite, &headers.user.uuid, conn)
.await?;
Ok(())
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct ImportData {
- Ciphers: Vec<CipherData>,
- Folders: Vec<FolderData>,
- FolderRelationships: Vec<RelationsData>,
+ ciphers: Vec<CipherData>,
+ folders: Vec<FolderData>,
+ folder_relationships: Vec<RelationsData>,
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct RelationsData {
// Cipher id
- Key: usize,
+ key: usize,
// Folder id
- Value: usize,
+ value: usize,
}
#[post("/ciphers/import", data = "<data>")]
async fn post_ciphers_import(
- data: JsonUpcase<ImportData>,
+ data: Json<ImportData>,
headers: Headers,
conn: DbConn,
) -> EmptyResult {
enforce_personal_ownership_policy(None, &headers, &conn).await?;
- let data: ImportData = data.into_inner().data;
+ let data: ImportData = data.into_inner();
// Validate the import before continuing
// Bitwarden does not process the import if there is one item invalid.
// Since we check for the size of the encrypted note length, we need to do that here to pre-validate it.
// TODO: See if we can optimize the whole cipher adding/importing and prevent duplicate code and checks.
- Cipher::validate_notes(&data.Ciphers)?;
- // Read and create the folders
- let mut folders: Vec<_> = Vec::new();
- for folder in data.Folders {
- let mut new_folder = Folder::new(headers.user.uuid.clone(), folder.Name);
- new_folder.save(&conn).await?;
- folders.push(new_folder);
+ Cipher::validate_notes(&data.ciphers)?;
+ let existing_folders: Vec<String> = Folder::find_by_user(&headers.user.uuid, &conn)
+ .await
+ .into_iter()
+ .map(|f| f.uuid)
+ .collect();
+ let mut folders: Vec<String> = Vec::with_capacity(data.folders.len());
+ for folder in data.folders {
+ let folder_uuid =
+ if folder.id.is_some() && existing_folders.contains(folder.id.as_ref().unwrap()) {
+ folder.id.unwrap()
+ } else {
+ let mut new_folder = Folder::new(headers.user.uuid.clone(), folder.name);
+ new_folder.save(&conn).await?;
+ new_folder.uuid
+ };
+ folders.push(folder_uuid);
}
// Read the relations between folders and ciphers
- let mut relations_map = HashMap::new();
- for relation in data.FolderRelationships {
- relations_map.insert(relation.Key, relation.Value);
+ let mut relations_map = HashMap::with_capacity(data.folder_relationships.len());
+ for relation in data.folder_relationships {
+ relations_map.insert(relation.key, relation.value);
}
// Read and create the ciphers
- for (index, mut cipher_data) in data.Ciphers.into_iter().enumerate() {
- let folder_uuid = relations_map.get(&index).map(|i| folders[*i].uuid.clone());
- cipher_data.FolderId = folder_uuid;
- let mut cipher = Cipher::new(cipher_data.Type, cipher_data.Name.clone());
- update_cipher_from_data(&mut cipher, cipher_data, &headers, false, &conn, true).await?;
+ for (index, mut cipher_data) in data.ciphers.into_iter().enumerate() {
+ let folder_uuid = relations_map.get(&index).map(|i| folders[*i].clone());
+ cipher_data.folder_id = folder_uuid;
+ let mut cipher = Cipher::new(cipher_data.r#type, cipher_data.name.clone());
+ update_cipher_from_data(&mut cipher, cipher_data, &headers, None, &conn, true).await?;
}
let mut user = headers.user;
user.update_revision(&conn).await?;
@@ -510,7 +531,7 @@ async fn post_ciphers_import(
#[put("/ciphers/<uuid>/admin", data = "<data>")]
async fn put_cipher_admin(
uuid: &str,
- data: JsonUpcase<CipherData>,
+ data: Json<CipherData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
@@ -520,7 +541,7 @@ async fn put_cipher_admin(
#[post("/ciphers/<uuid>/admin", data = "<data>")]
async fn post_cipher_admin(
uuid: &str,
- data: JsonUpcase<CipherData>,
+ data: Json<CipherData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
@@ -530,7 +551,7 @@ async fn post_cipher_admin(
#[post("/ciphers/<uuid>", data = "<data>")]
async fn post_cipher(
uuid: &str,
- data: JsonUpcase<CipherData>,
+ data: Json<CipherData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
@@ -540,11 +561,11 @@ async fn post_cipher(
#[put("/ciphers/<uuid>", data = "<data>")]
async fn put_cipher(
uuid: &str,
- data: JsonUpcase<CipherData>,
+ data: Json<CipherData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
- let data: CipherData = data.into_inner().data;
+ let data: CipherData = data.into_inner();
let Some(mut cipher) = Cipher::find_by_uuid(uuid, &conn).await else {
err!("Cipher doesn't exist")
};
@@ -558,7 +579,7 @@ async fn put_cipher(
{
err!("Cipher is not write accessible")
}
- update_cipher_from_data(&mut cipher, data, &headers, false, &conn, false).await?;
+ update_cipher_from_data(&mut cipher, data, &headers, None, &conn, false).await?;
Ok(Json(
cipher
.to_json(&headers.user.uuid, None, CipherSyncType::User, &conn)
@@ -569,7 +590,7 @@ async fn put_cipher(
#[post("/ciphers/<uuid>/partial", data = "<data>")]
async fn post_cipher_partial(
uuid: &str,
- data: JsonUpcase<PartialCipherData>,
+ data: Json<PartialCipherData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
@@ -580,15 +601,15 @@ async fn post_cipher_partial(
#[put("/ciphers/<uuid>/partial", data = "<data>")]
async fn put_cipher_partial(
uuid: &str,
- data: JsonUpcase<PartialCipherData>,
+ data: Json<PartialCipherData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
- let data: PartialCipherData = data.into_inner().data;
+ let data: PartialCipherData = data.into_inner();
let Some(cipher) = Cipher::find_by_uuid(uuid, &conn).await else {
err!("Cipher doesn't exist")
};
- if let Some(ref folder_id) = data.FolderId {
+ if let Some(ref folder_id) = data.folder_id {
match Folder::find_by_uuid(folder_id, &conn).await {
Some(folder) => {
if folder.user_uuid != headers.user.uuid {
@@ -600,11 +621,11 @@ async fn put_cipher_partial(
}
// Move cipher
cipher
- .move_to_folder(data.FolderId.clone(), &headers.user.uuid, &conn)
+ .move_to_folder(data.folder_id.clone(), &headers.user.uuid, &conn)
.await?;
// Update favorite
cipher
- .set_favorite(Some(data.Favorite), &headers.user.uuid, &conn)
+ .set_favorite(Some(data.favorite), &headers.user.uuid, &conn)
.await?;
Ok(Json(
cipher
@@ -614,35 +635,103 @@ async fn put_cipher_partial(
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct CollectionsAdminData {
- CollectionIds: Vec<String>,
+ collection_ids: Vec<String>,
+}
+
+#[put("/ciphers/<uuid>/collections_v2", data = "<data>")]
+async fn put_collections2_update(
+ uuid: &str,
+ data: Json<CollectionsAdminData>,
+ headers: Headers,
+ conn: DbConn,
+) -> JsonResult {
+ post_collections2_update(uuid, data, headers, conn).await
+}
+
+#[post("/ciphers/<uuid>/collections_v2", data = "<data>")]
+async fn post_collections2_update(
+ uuid: &str,
+ data: Json<CollectionsAdminData>,
+ headers: Headers,
+ conn: DbConn,
+) -> JsonResult {
+ let cipher_details = post_collections_update(uuid, data, headers, conn).await?;
+ Ok(Json(json!({
+ "object": "optionalCipherDetails",
+ "unavailable": false,
+ "cipher": *cipher_details
+ })))
}
#[put("/ciphers/<uuid>/collections", data = "<data>")]
async fn put_collections_update(
uuid: &str,
- data: JsonUpcase<CollectionsAdminData>,
+ data: Json<CollectionsAdminData>,
headers: Headers,
conn: DbConn,
-) -> EmptyResult {
- post_collections_admin(uuid, data, headers, conn).await
+) -> JsonResult {
+ post_collections_update(uuid, data, headers, conn).await
}
#[post("/ciphers/<uuid>/collections", data = "<data>")]
async fn post_collections_update(
uuid: &str,
- data: JsonUpcase<CollectionsAdminData>,
+ data: Json<CollectionsAdminData>,
headers: Headers,
conn: DbConn,
-) -> EmptyResult {
- post_collections_admin(uuid, data, headers, conn).await
+) -> JsonResult {
+ let data: CollectionsAdminData = data.into_inner();
+ let Some(cipher) = Cipher::find_by_uuid(uuid, &conn).await else {
+ err!("Cipher doesn't exist")
+ };
+ if !cipher
+ .is_write_accessible_to_user(&headers.user.uuid, &conn)
+ .await
+ {
+ err!("Cipher is not write accessible")
+ }
+
+ let posted_collections = HashSet::<String>::from_iter(data.collection_ids);
+ let current_collections = HashSet::<String>::from_iter(
+ cipher
+ .get_collections(headers.user.uuid.clone(), &conn)
+ .await,
+ );
+
+ for collection in posted_collections.symmetric_difference(¤t_collections) {
+ match Collection::find_by_uuid(collection, &conn).await {
+ None => err!("Invalid collection ID provided"),
+ Some(collection) => {
+ if collection
+ .is_writable_by_user(&headers.user.uuid, &conn)
+ .await
+ {
+ if posted_collections.contains(&collection.uuid) {
+ // Add to collection
+ CollectionCipher::save(&cipher.uuid, &collection.uuid, &conn).await?;
+ } else {
+ // Remove from collection
+ CollectionCipher::delete(&cipher.uuid, &collection.uuid, &conn).await?;
+ }
+ } else {
+ err!("No rights to modify the collection")
+ }
+ }
+ }
+ }
+ Ok(Json(
+ cipher
+ .to_json(&headers.user.uuid, None, CipherSyncType::User, &conn)
+ .await,
+ ))
}
#[put("/ciphers/<uuid>/collections-admin", data = "<data>")]
async fn put_collections_admin(
uuid: &str,
- data: JsonUpcase<CollectionsAdminData>,
+ data: Json<CollectionsAdminData>,
headers: Headers,
conn: DbConn,
) -> EmptyResult {
@@ -652,11 +741,11 @@ async fn put_collections_admin(
#[post("/ciphers/<uuid>/collections-admin", data = "<data>")]
async fn post_collections_admin(
uuid: &str,
- data: JsonUpcase<CollectionsAdminData>,
+ data: Json<CollectionsAdminData>,
headers: Headers,
conn: DbConn,
) -> EmptyResult {
- let data: CollectionsAdminData = data.into_inner().data;
+ let data: CollectionsAdminData = data.into_inner();
let Some(cipher) = Cipher::find_by_uuid(uuid, &conn).await else {
err!("Cipher doesn't exist")
};
@@ -666,14 +755,12 @@ async fn post_collections_admin(
{
err!("Cipher is not write accessible")
}
-
- let posted_collections: HashSet<String> = data.CollectionIds.iter().cloned().collect();
- let current_collections: HashSet<String> = cipher
- .get_collections(headers.user.uuid.clone(), &conn)
- .await
- .iter()
- .cloned()
- .collect();
+ let posted_collections = HashSet::<String>::from_iter(data.collection_ids);
+ let current_collections = HashSet::<String>::from_iter(
+ cipher
+ .get_admin_collections(headers.user.uuid.clone(), &conn)
+ .await,
+ );
for collection in posted_collections.symmetric_difference(¤t_collections) {
match Collection::find_by_uuid(collection, &conn).await {
None => err!("Invalid collection ID provided"),
@@ -699,65 +786,67 @@ async fn post_collections_admin(
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct ShareCipherData {
- Cipher: CipherData,
- CollectionIds: Vec<String>,
+ #[serde(alias = "Cipher")]
+ cipher: CipherData,
+ #[serde(alias = "CollectionIds")]
+ collection_ids: Vec<String>,
}
#[post("/ciphers/<uuid>/share", data = "<data>")]
async fn post_cipher_share(
uuid: &str,
- data: JsonUpcase<ShareCipherData>,
+ data: Json<ShareCipherData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
- let data: ShareCipherData = data.into_inner().data;
+ let data: ShareCipherData = data.into_inner();
share_cipher_by_uuid(uuid, data, &headers, &conn).await
}
#[put("/ciphers/<uuid>/share", data = "<data>")]
async fn put_cipher_share(
uuid: &str,
- data: JsonUpcase<ShareCipherData>,
+ data: Json<ShareCipherData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
- let data: ShareCipherData = data.into_inner().data;
+ let data: ShareCipherData = data.into_inner();
share_cipher_by_uuid(uuid, data, &headers, &conn).await
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct ShareSelectedCipherData {
- Ciphers: Vec<CipherData>,
- CollectionIds: Vec<String>,
+ ciphers: Vec<CipherData>,
+ collection_ids: Vec<String>,
}
#[put("/ciphers/share", data = "<data>")]
async fn put_cipher_share_selected(
- data: JsonUpcase<ShareSelectedCipherData>,
+ data: Json<ShareSelectedCipherData>,
headers: Headers,
conn: DbConn,
) -> EmptyResult {
- let mut data: ShareSelectedCipherData = data.into_inner().data;
- if data.Ciphers.is_empty() {
+ let mut data: ShareSelectedCipherData = data.into_inner();
+ if data.ciphers.is_empty() {
err!("You must select at least one cipher.")
}
- if data.CollectionIds.is_empty() {
+ if data.collection_ids.is_empty() {
err!("You must select at least one collection.")
}
- for cipher in &data.Ciphers {
- if cipher.Id.is_none() {
+ for cipher in &data.ciphers {
+ if cipher.id.is_none() {
err!("Request missing ids field");
}
}
- while let Some(cipher) = data.Ciphers.pop() {
+ while let Some(cipher) = data.ciphers.pop() {
let mut shared_cipher_data = ShareCipherData {
- Cipher: cipher,
- CollectionIds: data.CollectionIds.clone(),
+ cipher,
+ collection_ids: data.collection_ids.clone(),
};
- match shared_cipher_data.Cipher.Id.take() {
+ match shared_cipher_data.cipher.id.take() {
Some(id) => share_cipher_by_uuid(&id, shared_cipher_data, &headers, &conn).await?,
None => err!("Request missing ids field"),
};
@@ -784,9 +873,9 @@ async fn share_cipher_by_uuid(
}
None => err!("Cipher doesn't exist"),
};
- let mut shared_to_collection = false;
- if let Some(ref organization_uuid) = data.Cipher.OrganizationId {
- for col_uuid in &data.CollectionIds {
+ let mut shared_to_collections = Vec::new();
+ if let Some(ref organization_uuid) = data.cipher.organization_id {
+ for col_uuid in &data.collection_ids {
match Collection::find_by_uuid_and_org(col_uuid, organization_uuid, conn).await {
None => err!("Invalid collection ID provided"),
Some(collection) => {
@@ -795,7 +884,7 @@ async fn share_cipher_by_uuid(
.await
{
CollectionCipher::save(&cipher.uuid, &collection.uuid, conn).await?;
- shared_to_collection = true;
+ shared_to_collections.push(collection.uuid);
} else {
err!("No rights to modify the collection")
}
@@ -805,9 +894,9 @@ async fn share_cipher_by_uuid(
};
update_cipher_from_data(
&mut cipher,
- data.Cipher,
+ data.cipher,
headers,
- shared_to_collection,
+ Some(shared_to_collections),
conn,
false,
)
@@ -833,12 +922,16 @@ fn get_attachment(uuid: &str, attachment_id: &str, _headers: Headers) -> Error {
}
#[derive(Deserialize)]
-#[allow(dead_code, non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct AttachmentRequestData {
- Key: String,
- FileName: String,
- FileSize: i32,
- AdminRequest: Option<bool>, // true when attaching from an org vault view
+ #[allow(dead_code)]
+ key: String,
+ #[allow(dead_code)]
+ file_name: String,
+ #[allow(dead_code)]
+ file_size: NumberOrString,
+ #[allow(dead_code)]
+ admin_request: Option<bool>, // true when attaching from an org vault view
}
/// v2 API for creating an attachment associated with a cipher.
@@ -847,11 +940,7 @@ struct AttachmentRequestData {
/// For self-hosted instances, it's another API on the local instance.
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/ciphers/<uuid>/attachment/v2", data = "<data>")]
-fn post_attachment_v2(
- uuid: &str,
- data: JsonUpcase<AttachmentRequestData>,
- _headers: Headers,
-) -> Error {
+fn post_attachment_v2(uuid: &str, data: Json<AttachmentRequestData>, _headers: Headers) -> Error {
Error::new(ATTACHMENTS_DISABLED_MSG, ATTACHMENTS_DISABLED_MSG)
}
@@ -974,7 +1063,7 @@ async fn delete_cipher_admin(uuid: &str, headers: Headers, conn: DbConn) -> Empt
#[delete("/ciphers", data = "<data>")]
async fn delete_cipher_selected(
- data: JsonUpcase<Value>,
+ data: Json<CipherIdsData>,
headers: Headers,
conn: DbConn,
) -> EmptyResult {
@@ -983,7 +1072,7 @@ async fn delete_cipher_selected(
#[post("/ciphers/delete", data = "<data>")]
async fn delete_cipher_selected_post(
- data: JsonUpcase<Value>,
+ data: Json<CipherIdsData>,
headers: Headers,
conn: DbConn,
) -> EmptyResult {
@@ -992,7 +1081,7 @@ async fn delete_cipher_selected_post(
#[put("/ciphers/delete", data = "<data>")]
async fn delete_cipher_selected_put(
- data: JsonUpcase<Value>,
+ data: Json<CipherIdsData>,
headers: Headers,
conn: DbConn,
) -> EmptyResult {
@@ -1001,7 +1090,7 @@ async fn delete_cipher_selected_put(
#[delete("/ciphers/admin", data = "<data>")]
async fn delete_cipher_selected_admin(
- data: JsonUpcase<Value>,
+ data: Json<CipherIdsData>,
headers: Headers,
conn: DbConn,
) -> EmptyResult {
@@ -1010,7 +1099,7 @@ async fn delete_cipher_selected_admin(
#[post("/ciphers/delete-admin", data = "<data>")]
async fn delete_cipher_selected_post_admin(
- data: JsonUpcase<Value>,
+ data: Json<CipherIdsData>,
headers: Headers,
conn: DbConn,
) -> EmptyResult {
@@ -1019,7 +1108,7 @@ async fn delete_cipher_selected_post_admin(
#[put("/ciphers/delete-admin", data = "<data>")]
async fn delete_cipher_selected_put_admin(
- data: JsonUpcase<Value>,
+ data: Json<CipherIdsData>,
headers: Headers,
conn: DbConn,
) -> EmptyResult {
@@ -1038,7 +1127,7 @@ async fn restore_cipher_put_admin(uuid: &str, headers: Headers, conn: DbConn) ->
#[put("/ciphers/restore", data = "<data>")]
async fn restore_cipher_selected(
- data: JsonUpcase<Value>,
+ data: Json<CipherIdsData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
@@ -1046,21 +1135,21 @@ async fn restore_cipher_selected(
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct MoveCipherData {
- FolderId: Option<String>,
- Ids: Vec<String>,
+ folder_id: Option<String>,
+ ids: Vec<String>,
}
#[post("/ciphers/move", data = "<data>")]
async fn move_cipher_selected(
- data: JsonUpcase<MoveCipherData>,
+ data: Json<MoveCipherData>,
headers: Headers,
conn: DbConn,
) -> EmptyResult {
- let data = data.into_inner().data;
+ let data = data.into_inner();
let user_uuid = headers.user.uuid;
- if let Some(ref folder_id) = data.FolderId {
+ if let Some(ref folder_id) = data.folder_id {
match Folder::find_by_uuid(folder_id, &conn).await {
Some(folder) => {
if folder.user_uuid != user_uuid {
@@ -1070,7 +1159,7 @@ async fn move_cipher_selected(
None => err!("Folder doesn't exist"),
}
}
- for uuid in data.Ids {
+ for uuid in data.ids {
let Some(cipher) = Cipher::find_by_uuid(&uuid, &conn).await else {
err!("Cipher doesn't exist")
};
@@ -1079,7 +1168,7 @@ async fn move_cipher_selected(
}
// Move cipher
cipher
- .move_to_folder(data.FolderId.clone(), &user_uuid, &conn)
+ .move_to_folder(data.folder_id.clone(), &user_uuid, &conn)
.await?;
}
Ok(())
@@ -1087,7 +1176,7 @@ async fn move_cipher_selected(
#[put("/ciphers/move", data = "<data>")]
async fn move_cipher_selected_put(
- data: JsonUpcase<MoveCipherData>,
+ data: Json<MoveCipherData>,
headers: Headers,
conn: DbConn,
) -> EmptyResult {
@@ -1103,11 +1192,11 @@ struct OrganizationId {
#[post("/ciphers/purge?<organization..>", data = "<data>")]
async fn delete_all(
organization: Option<OrganizationId>,
- data: JsonUpcase<PasswordOrOtpData>,
+ data: Json<PasswordOrOtpData>,
headers: Headers,
conn: DbConn,
) -> EmptyResult {
- let data: PasswordOrOtpData = data.into_inner().data;
+ let data: PasswordOrOtpData = data.into_inner();
let mut user = headers.user;
data.validate(&user)?;
if let Some(org_data) = organization {
@@ -1162,22 +1251,21 @@ async fn _delete_cipher_by_uuid(
Ok(())
}
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct CipherIdsData {
+ ids: Vec<String>,
+}
+
async fn _delete_multiple_ciphers(
- data: JsonUpcase<Value>,
+ data: Json<CipherIdsData>,
headers: Headers,
conn: DbConn,
soft_delete: bool,
) -> EmptyResult {
- let data: Value = data.into_inner().data;
- let uuids = match data.get("Ids") {
- Some(ids) => match ids.as_array() {
- Some(ids) => ids.iter().filter_map(Value::as_str),
- None => err!("Posted ids field is not an array"),
- },
- None => err!("Request missing ids field"),
- };
- for uuid in uuids {
- if let error @ Err(_) = _delete_cipher_by_uuid(uuid, &headers, &conn, soft_delete).await {
+ let data = data.into_inner();
+ for uuid in data.ids {
+ if let error @ Err(_) = _delete_cipher_by_uuid(&uuid, &headers, &conn, soft_delete).await {
return error;
};
}
@@ -1204,29 +1292,22 @@ async fn _restore_cipher_by_uuid(uuid: &str, headers: &Headers, conn: &DbConn) -
}
async fn _restore_multiple_ciphers(
- data: JsonUpcase<Value>,
+ data: Json<CipherIdsData>,
headers: &Headers,
conn: &DbConn,
) -> JsonResult {
- let data: Value = data.into_inner().data;
- let uuids = match data.get("Ids") {
- Some(ids) => match ids.as_array() {
- Some(ids) => ids.iter().filter_map(Value::as_str),
- None => err!("Posted ids field is not an array"),
- },
- None => err!("Request missing ids field"),
- };
+ let data = data.into_inner();
let mut ciphers: Vec<Value> = Vec::new();
- for uuid in uuids {
- match _restore_cipher_by_uuid(uuid, headers, conn).await {
+ for uuid in data.ids {
+ match _restore_cipher_by_uuid(&uuid, headers, conn).await {
Ok(json) => ciphers.push(json.into_inner()),
err => return err,
}
}
Ok(Json(json!({
- "Data": ciphers,
- "Object": "list",
- "ContinuationToken": null
+ "data": ciphers,
+ "object": "list",
+ "continuationToken": null
})))
}
/// This will hold all the necessary data to improve a full sync of all the ciphers
diff --git a/src/api/core/emergency_access.rs b/src/api/core/emergency_access.rs
@@ -1,8 +1,4 @@
-use crate::{
- api::{JsonUpcase, NumberOrString},
- auth::Headers,
- error::Error,
-};
+use crate::{auth::Headers, error::Error, util::NumberOrString};
use rocket::{serde::json::Json, Route};
use serde_json::Value;
@@ -32,43 +28,54 @@ pub fn routes() -> Vec<Route> {
#[get("/emergency-access/trusted")]
fn get_contacts(_headers: Headers) -> Json<Value> {
Json(json!({
- "Data": Vec::<Value>::new(),
- "Object": "list",
- "ContinuationToken": null
+ "data": Vec::<Value>::new(),
+ "object": "list",
+ "continuationToken": null
}))
}
#[allow(clippy::needless_pass_by_value)]
#[get("/emergency-access/granted")]
fn get_grantees(_headers: Headers) -> Json<Value> {
Json(json!({
- "Data": Vec::<Value>::new(),
- "Object": "list",
- "ContinuationToken": null
+ "data": Vec::<Value>::new(),
+ "object": "list",
+ "continuationToken": null
}))
}
const ACCESS_NOT_ALLOWED_MSG: &str = "Emergency access is not allowed.";
-#[allow(unused_variables)]
+#[allow(unused_variables, clippy::needless_pass_by_value)]
#[get("/emergency-access/<emer_id>")]
-fn get_emergency_access(emer_id: &str) -> Error {
+fn get_emergency_access(emer_id: &str, _headers: Headers) -> Error {
Error::new(ACCESS_NOT_ALLOWED_MSG, ACCESS_NOT_ALLOWED_MSG)
}
#[derive(Deserialize)]
-#[allow(dead_code, non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct EmergencyAccessUpdateData {
- Type: NumberOrString,
- WaitTimeDays: i32,
- KeyEncrypted: Option<String>,
+ #[allow(dead_code)]
+ r#type: NumberOrString,
+ #[allow(dead_code)]
+ wait_time_days: i32,
+ #[allow(dead_code)]
+ key_encrypted: Option<String>,
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[put("/emergency-access/<emer_id>", data = "<data>")]
-fn put_emergency_access(emer_id: &str, data: JsonUpcase<EmergencyAccessUpdateData>) -> Error {
+fn put_emergency_access(
+ emer_id: &str,
+ data: Json<EmergencyAccessUpdateData>,
+ _headers: Headers,
+) -> Error {
Error::new(ACCESS_NOT_ALLOWED_MSG, ACCESS_NOT_ALLOWED_MSG)
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/emergency-access/<emer_id>", data = "<data>")]
-fn post_emergency_access(emer_id: &str, data: JsonUpcase<EmergencyAccessUpdateData>) -> Error {
+fn post_emergency_access(
+ emer_id: &str,
+ data: Json<EmergencyAccessUpdateData>,
+ _headers: Headers,
+) -> Error {
Error::new(ACCESS_NOT_ALLOWED_MSG, ACCESS_NOT_ALLOWED_MSG)
}
@@ -85,16 +92,19 @@ fn post_delete_emergency_access(emer_id: &str, _headers: Headers) -> Error {
}
#[derive(Deserialize)]
-#[allow(dead_code, non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct EmergencyAccessInviteData {
- Email: String,
- Type: NumberOrString,
- WaitTimeDays: i32,
+ #[allow(dead_code)]
+ email: String,
+ #[allow(dead_code)]
+ r#type: NumberOrString,
+ #[allow(dead_code)]
+ wait_time_days: i32,
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/emergency-access/invite", data = "<data>")]
-fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, _headers: Headers) -> Error {
+fn send_invite(data: Json<EmergencyAccessInviteData>, _headers: Headers) -> Error {
Error::new(ACCESS_NOT_ALLOWED_MSG, ACCESS_NOT_ALLOWED_MSG)
}
@@ -105,30 +115,28 @@ fn resend_invite(emer_id: &str, _headers: Headers) -> Error {
}
#[derive(Deserialize)]
-#[allow(dead_code, non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct AcceptData {
- Token: String,
+ #[allow(dead_code)]
+ token: String,
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/emergency-access/<emer_id>/accept", data = "<data>")]
-fn accept_invite(emer_id: &str, data: JsonUpcase<AcceptData>, _headers: Headers) -> Error {
+fn accept_invite(emer_id: &str, data: Json<AcceptData>, _headers: Headers) -> Error {
Error::new(ACCESS_NOT_ALLOWED_MSG, ACCESS_NOT_ALLOWED_MSG)
}
#[derive(Deserialize)]
-#[allow(dead_code, non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct ConfirmData {
- Key: String,
+ #[allow(dead_code)]
+ key: String,
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/emergency-access/<emer_id>/confirm", data = "<data>")]
-fn confirm_emergency_access(
- emer_id: &str,
- data: JsonUpcase<ConfirmData>,
- _headers: Headers,
-) -> Error {
+fn confirm_emergency_access(emer_id: &str, data: Json<ConfirmData>, _headers: Headers) -> Error {
Error::new(ACCESS_NOT_ALLOWED_MSG, ACCESS_NOT_ALLOWED_MSG)
}
@@ -163,17 +171,19 @@ fn takeover_emergency_access(emer_id: &str, _headers: Headers) -> Error {
}
#[derive(Deserialize)]
-#[allow(dead_code, non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct EmergencyAccessPasswordData {
- NewMasterPasswordHash: String,
- Key: String,
+ #[allow(dead_code)]
+ new_master_password_hash: String,
+ #[allow(dead_code)]
+ key: String,
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/emergency-access/<emer_id>/password", data = "<data>")]
fn password_emergency_access(
emer_id: &str,
- data: JsonUpcase<EmergencyAccessPasswordData>,
+ data: Json<EmergencyAccessPasswordData>,
_headers: Headers,
) -> Error {
Error::new(ACCESS_NOT_ALLOWED_MSG, ACCESS_NOT_ALLOWED_MSG)
diff --git a/src/api/core/events.rs b/src/api/core/events.rs
@@ -1,7 +1,4 @@
-use crate::{
- api::JsonUpcaseVec,
- auth::{AdminHeaders, Headers},
-};
+use crate::auth::{AdminHeaders, Headers};
use rocket::{form::FromForm, serde::json::Json, Route};
use serde_json::Value;
pub fn routes() -> Vec<Route> {
@@ -9,10 +6,12 @@ pub fn routes() -> Vec<Route> {
}
#[derive(FromForm)]
-#[allow(non_snake_case, dead_code)]
struct EventRange {
+ #[allow(dead_code)]
start: String,
+ #[allow(dead_code)]
end: String,
+ #[allow(dead_code)]
#[field(name = "continuationToken")]
continuation_token: Option<String>,
}
@@ -22,9 +21,9 @@ struct EventRange {
#[get("/organizations/<org_id>/events?<data..>")]
fn get_org_events(org_id: &str, data: EventRange, _headers: AdminHeaders) -> Json<Value> {
Json(json!({
- "Data": Vec::<Value>::new(),
- "Object": "list",
- "ContinuationToken": None::<&str>,
+ "data": Vec::<Value>::new(),
+ "object": "list",
+ "continuationToken": None::<&str>,
}))
}
@@ -32,9 +31,9 @@ fn get_org_events(org_id: &str, data: EventRange, _headers: AdminHeaders) -> Jso
#[get("/ciphers/<cipher_id>/events?<data..>")]
fn get_cipher_events(cipher_id: &str, data: EventRange, _headers: Headers) -> Json<Value> {
Json(json!({
- "Data": Vec::<Value>::new(),
- "Object": "list",
- "ContinuationToken": None::<&str>,
+ "data": Vec::<Value>::new(),
+ "object": "list",
+ "continuationToken": None::<&str>,
}))
}
@@ -47,9 +46,9 @@ fn get_user_events(
_headers: AdminHeaders,
) -> Json<Value> {
Json(json!({
- "Data": Vec::<Value>::new(),
- "Object": "list",
- "ContinuationToken": None::<&str>,
+ "data": Vec::<Value>::new(),
+ "object": "list",
+ "continuationToken": None::<&str>,
}))
}
pub fn main_routes() -> Vec<Route> {
@@ -57,11 +56,10 @@ pub fn main_routes() -> Vec<Route> {
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
struct EventCollection;
// Upstream:
// https://github.com/bitwarden/server/blob/8a22c0479e987e756ce7412c48a732f9002f0a2d/src/Events/Controllers/CollectController.cs
// https://github.com/bitwarden/server/blob/8a22c0479e987e756ce7412c48a732f9002f0a2d/src/Core/Services/Implementations/EventService.cs
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/collect", format = "application/json", data = "<data>")]
-fn post_events_collect(data: JsonUpcaseVec<EventCollection>, _headers: Headers) {}
+fn post_events_collect(data: Json<Vec<EventCollection>>, _headers: Headers) {}
diff --git a/src/api/core/folders.rs b/src/api/core/folders.rs
@@ -1,5 +1,5 @@
use crate::{
- api::{EmptyResult, JsonResult, JsonUpcase},
+ api::{EmptyResult, JsonResult},
auth::Headers,
db::{models::Folder, DbConn},
};
@@ -23,9 +23,9 @@ async fn get_folders(headers: Headers, conn: DbConn) -> Json<Value> {
let folders = Folder::find_by_user(&headers.user.uuid, &conn).await;
let folders_json: Vec<Value> = folders.iter().map(Folder::to_json).collect();
Json(json!({
- "Data": folders_json,
- "Object": "list",
- "ContinuationToken": null,
+ "data": folders_json,
+ "object": "list",
+ "continuationToken": null,
}))
}
@@ -41,15 +41,16 @@ async fn get_folder(uuid: &str, headers: Headers, conn: DbConn) -> JsonResult {
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
pub struct FolderData {
- pub Name: String,
+ pub name: String,
+ pub id: Option<String>,
}
#[post("/folders", data = "<data>")]
-async fn post_folders(data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn) -> JsonResult {
- let data: FolderData = data.into_inner().data;
- let mut folder = Folder::new(headers.user.uuid, data.Name);
+async fn post_folders(data: Json<FolderData>, headers: Headers, conn: DbConn) -> JsonResult {
+ let data: FolderData = data.into_inner();
+ let mut folder = Folder::new(headers.user.uuid, data.name);
folder.save(&conn).await?;
Ok(Json(folder.to_json()))
}
@@ -57,7 +58,7 @@ async fn post_folders(data: JsonUpcase<FolderData>, headers: Headers, conn: DbCo
#[post("/folders/<uuid>", data = "<data>")]
async fn post_folder(
uuid: &str,
- data: JsonUpcase<FolderData>,
+ data: Json<FolderData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
@@ -67,18 +68,18 @@ async fn post_folder(
#[put("/folders/<uuid>", data = "<data>")]
async fn put_folder(
uuid: &str,
- data: JsonUpcase<FolderData>,
+ data: Json<FolderData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
- let data: FolderData = data.into_inner().data;
+ let data: FolderData = data.into_inner();
let Some(mut folder) = Folder::find_by_uuid(uuid, &conn).await else {
err!("Invalid folder")
};
if folder.user_uuid != headers.user.uuid {
err!("Folder belongs to another user")
}
- folder.name = data.Name;
+ folder.name = data.name;
folder.save(&conn).await?;
Ok(Json(folder.to_json()))
}
diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs
@@ -40,20 +40,16 @@ pub fn events_routes() -> Vec<Route> {
//
// Move this somewhere else
//
-use crate::{
- api::{JsonResult, JsonUpcase},
- auth::Headers,
- db::DbConn,
-};
+use crate::{api::JsonResult, auth::Headers, db::DbConn};
use rocket::{serde::json::Json, Catcher, Route};
use serde_json::Value;
#[derive(Serialize, Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct GlobalDomain {
- Type: i32,
- Domains: Vec<String>,
- Excluded: bool,
+ r#type: i32,
+ domains: Vec<String>,
+ excluded: bool,
}
const GLOBAL_DOMAINS: &str = include_str!("../../static/global_domains.json");
@@ -70,34 +66,34 @@ fn _get_eq_domains(headers: Headers, no_excluded: bool) -> Json<Value> {
let excluded_globals: Vec<i32> = from_str(&user.excluded_globals).unwrap();
let mut globals: Vec<GlobalDomain> = from_str(GLOBAL_DOMAINS).unwrap();
for global in &mut globals {
- global.Excluded = excluded_globals.contains(&global.Type);
+ global.excluded = excluded_globals.contains(&global.r#type);
}
if no_excluded {
- globals.retain(|g| !g.Excluded);
+ globals.retain(|g| !g.excluded);
}
Json(json!({
- "EquivalentDomains": equivalent_domains,
- "GlobalEquivalentDomains": globals,
- "Object": "domains",
+ "equivalentDomains": equivalent_domains,
+ "globalEquivalentDomains": globals,
+ "object": "domains",
}))
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct EquivDomainData {
- ExcludedGlobalEquivalentDomains: Option<Vec<i32>>,
- EquivalentDomains: Option<Vec<Vec<String>>>,
+ excluded_global_equivalent_domains: Option<Vec<i32>>,
+ equivalent_domains: Option<Vec<Vec<String>>>,
}
#[post("/settings/domains", data = "<data>")]
async fn post_eq_domains(
- data: JsonUpcase<EquivDomainData>,
+ data: Json<EquivDomainData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
- let data: EquivDomainData = data.into_inner().data;
- let excluded_globals = data.ExcludedGlobalEquivalentDomains.unwrap_or_default();
- let equivalent_domains = data.EquivalentDomains.unwrap_or_default();
+ let data: EquivDomainData = data.into_inner();
+ let excluded_globals = data.excluded_global_equivalent_domains.unwrap_or_default();
+ let equivalent_domains = data.equivalent_domains.unwrap_or_default();
let mut user = headers.user;
use serde_json::to_string;
user.excluded_globals = to_string(&excluded_globals).unwrap_or_else(|_| "[]".to_owned());
@@ -107,11 +103,7 @@ async fn post_eq_domains(
}
#[put("/settings/domains", data = "<data>")]
-async fn put_eq_domains(
- data: JsonUpcase<EquivDomainData>,
- headers: Headers,
- conn: DbConn,
-) -> JsonResult {
+async fn put_eq_domains(data: Json<EquivDomainData>, headers: Headers, conn: DbConn) -> JsonResult {
post_eq_domains(data, headers, conn).await
}
#[allow(unused_variables)]
@@ -142,12 +134,11 @@ fn config() -> Json<Value> {
// Note: The clients use this version to handle backwards compatibility concerns
// This means they expect a version that closely matches the Bitwarden server version
// We should make sure that we keep this updated when we support the new server features
- "version": "2023.12.1",
+ "version": "2024.2.0",
"gitHash": "",
"server": {
"name": "Vaultwarden",
"url": "https://github.com/dani-garcia/vaultwarden",
- "version": crate::VERSION
},
"environment": {
"vault": domain,
@@ -157,9 +148,8 @@ fn config() -> Json<Value> {
"sso": ""
},
"featureStates": {
- "autofill-overlay": true,
- "autofill-v2": true,
- "fido2-vault-credentials": true
+ "flexible-collections-v-1": false,
+ "key-rotation-improvements": true,
},
"object": "config",
}))
diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs
@@ -1,8 +1,7 @@
use crate::{
api::{
core::{CipherSyncData, CipherSyncType},
- EmptyResult, JsonResult, JsonUpcase, JsonUpcaseVec, JsonVec, NumberOrString,
- PasswordOrOtpData,
+ EmptyResult, JsonResult, PasswordOrOtpData,
},
auth::{self, AdminHeaders, Headers, ManagerHeaders, ManagerHeadersLoose, OwnerHeaders},
db::{
@@ -14,12 +13,12 @@ use crate::{
DbConn,
},
error::Error,
- util,
+ util::{self, NumberOrString},
};
use core::convert;
-use rocket::serde::json::Json;
-use rocket::Route;
+use rocket::{http::Status, serde::json::Json, Route};
use serde_json::Value;
+use std::collections::{HashMap, HashSet};
pub fn routes() -> Vec<Route> {
routes![
@@ -58,7 +57,7 @@ pub fn routes() -> Vec<Route> {
get_org_export,
get_org_users,
get_organization,
- get_organization_keys,
+ get_organization_public_key,
get_organization_tax,
get_plans,
get_plans_all,
@@ -72,6 +71,7 @@ pub fn routes() -> Vec<Route> {
leave_organization,
list_policies,
list_policies_token,
+ post_bulk_collections,
post_delete_group,
post_delete_group_user,
post_delete_organization,
@@ -105,69 +105,75 @@ pub fn routes() -> Vec<Route> {
}
#[derive(Deserialize)]
-#[allow(dead_code, non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct OrgData {
- BillingEmail: String,
- CollectionName: String,
- Key: String,
- Name: String,
- Keys: Option<OrgKeyData>,
- #[serde(rename = "PlanType")]
- _PlanType: NumberOrString, // Ignored, always use the same plan
+ #[allow(dead_code)]
+ billing_email: String,
+ #[allow(dead_code)]
+ collection_name: String,
+ #[allow(dead_code)]
+ key: String,
+ #[allow(dead_code)]
+ name: String,
+ #[allow(dead_code)]
+ keys: Option<OrgKeyData>,
+ #[allow(dead_code)]
+ plan_type: NumberOrString, // Ignored, always use the same plan
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct OrganizationUpdateData {
- BillingEmail: String,
- Name: String,
+ billing_email: String,
+ name: String,
}
#[derive(Deserialize)]
-#[allow(dead_code, non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct NewCollectionData {
- Name: String,
- Groups: Vec<NewCollectionObjectData>,
- Users: Vec<NewCollectionObjectData>,
- ExternalId: Option<String>,
+ name: String,
+ #[allow(dead_code)]
+ groups: Vec<NewCollectionObjectData>,
+ users: Vec<NewCollectionObjectData>,
+ external_id: Option<String>,
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct NewCollectionObjectData {
- HidePasswords: bool,
- Id: String,
- ReadOnly: bool,
+ hide_passwords: bool,
+ id: String,
+ read_only: bool,
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct OrgKeyData {
- EncryptedPrivateKey: String,
- PublicKey: String,
+ encrypted_private_key: String,
+ public_key: String,
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct OrgBulkIds {
- Ids: Vec<String>,
+ ids: Vec<String>,
}
const ORG_CREATION_NOT_ALLOWED_MSG: &str = "Organization creation is not allowed";
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/organizations", data = "<data>")]
-fn create_organization(_headers: Headers, data: JsonUpcase<OrgData>) -> Error {
+fn create_organization(_headers: Headers, data: Json<OrgData>) -> Error {
Error::new(ORG_CREATION_NOT_ALLOWED_MSG, ORG_CREATION_NOT_ALLOWED_MSG)
}
#[delete("/organizations/<org_id>", data = "<data>")]
async fn delete_organization(
org_id: &str,
- data: JsonUpcase<PasswordOrOtpData>,
+ data: Json<PasswordOrOtpData>,
headers: OwnerHeaders,
conn: DbConn,
) -> EmptyResult {
- let data: PasswordOrOtpData = data.into_inner().data;
+ let data: PasswordOrOtpData = data.into_inner();
data.validate(&headers.user)?;
match Organization::find_by_uuid(org_id, &conn).await {
None => err!("Organization not found"),
@@ -178,7 +184,7 @@ async fn delete_organization(
#[post("/organizations/<org_id>/delete", data = "<data>")]
async fn post_delete_organization(
org_id: &str,
- data: JsonUpcase<PasswordOrOtpData>,
+ data: Json<PasswordOrOtpData>,
headers: OwnerHeaders,
conn: DbConn,
) -> EmptyResult {
@@ -218,7 +224,7 @@ async fn get_organization(org_id: &str, _headers: OwnerHeaders, conn: DbConn) ->
async fn put_organization(
org_id: &str,
headers: OwnerHeaders,
- data: JsonUpcase<OrganizationUpdateData>,
+ data: Json<OrganizationUpdateData>,
conn: DbConn,
) -> JsonResult {
post_organization(org_id, headers, data, conn).await
@@ -228,15 +234,15 @@ async fn put_organization(
async fn post_organization(
org_id: &str,
_headers: OwnerHeaders,
- data: JsonUpcase<OrganizationUpdateData>,
+ data: Json<OrganizationUpdateData>,
conn: DbConn,
) -> JsonResult {
- let data: OrganizationUpdateData = data.into_inner().data;
+ let data: OrganizationUpdateData = data.into_inner();
let Some(mut org) = Organization::find_by_uuid(org_id, &conn).await else {
err!("Can't find organization details")
};
- org.name = data.Name;
- org.billing_email = data.BillingEmail;
+ org.name = data.name;
+ org.billing_email = data.billing_email;
org.save(&conn).await?;
Ok(Json(org.to_json()))
}
@@ -245,13 +251,13 @@ async fn post_organization(
#[get("/collections")]
async fn get_user_collections(headers: Headers, conn: DbConn) -> Json<Value> {
Json(json!({
- "Data":
+ "data":
Collection::find_by_user_uuid(headers.user.uuid.clone(), &conn).await
.iter()
.map(Collection::to_json)
.collect::<Value>(),
- "Object": "list",
- "ContinuationToken": null,
+ "object": "list",
+ "continuationToken": null,
}))
}
@@ -262,9 +268,9 @@ async fn get_org_collections(
conn: DbConn,
) -> Json<Value> {
Json(json!({
- "Data": _get_org_collections(org_id, &conn).await,
- "Object": "list",
- "ContinuationToken": null,
+ "data": _get_org_collections(org_id, &conn).await,
+ "object": "list",
+ "continuationToken": null,
}))
}
@@ -281,35 +287,28 @@ async fn get_org_collections_details(
err!("User is not part of organization")
};
let coll_users = CollectionUser::find_by_organization(org_id, &conn).await;
+ let has_full_access_to_org = user_org.access_all;
for col in Collection::find_by_organization(org_id, &conn).await {
- let groups: Vec<Value> = Vec::new();
- let mut assigned = false;
+ let assigned = has_full_access_to_org;
let users: Vec<Value> = coll_users
.iter()
.filter(|collection_user| collection_user.collection_uuid == col.uuid)
.map(|collection_user| {
- // Remember `user_uuid` is swapped here with the `user_org.uuid` with a join during the `CollectionUser::find_by_organization` call.
- // We check here if the current user is assigned to this collection or not.
- if collection_user.user_uuid == user_org.uuid {
- assigned = true;
- }
SelectionReadOnly::to_collection_user_details_read_only(collection_user).to_json()
})
.collect();
- if user_org.access_all {
- assigned = true;
- }
+ let groups = Vec::<Value>::new();
let mut json_object = col.to_json();
- json_object["Assigned"] = json!(assigned);
- json_object["Users"] = json!(users);
- json_object["Groups"] = json!(groups);
- json_object["Object"] = json!("collectionAccessDetails");
+ json_object["assigned"] = json!(assigned);
+ json_object["users"] = json!(users);
+ json_object["groups"] = json!(groups);
+ json_object["object"] = json!("collectionAccessDetails");
data.push(json_object);
}
Ok(Json(json!({
- "Data": data,
- "Object": "list",
- "ContinuationToken": null,
+ "data": data,
+ "object": "list",
+ "continuationToken": null,
})))
}
@@ -325,17 +324,17 @@ async fn _get_org_collections(org_id: &str, conn: &DbConn) -> Value {
async fn post_organization_collections(
org_id: &str,
headers: ManagerHeadersLoose,
- data: JsonUpcase<NewCollectionData>,
+ data: Json<NewCollectionData>,
conn: DbConn,
) -> JsonResult {
- let data: NewCollectionData = data.into_inner().data;
+ let data: NewCollectionData = data.into_inner();
let Some(org) = Organization::find_by_uuid(org_id, &conn).await else {
err!("Can't find organization details")
};
- let collection = Collection::new(org.uuid, data.Name, data.ExternalId);
+ let collection = Collection::new(org.uuid, data.name, data.external_id);
collection.save(&conn).await?;
- for user in data.Users {
- let Some(org_user) = UserOrganization::find_by_uuid(&user.Id, &conn).await else {
+ for user in data.users {
+ let Some(org_user) = UserOrganization::find_by_uuid(&user.id, &conn).await else {
err!("User is not part of organization")
};
if org_user.access_all {
@@ -344,8 +343,8 @@ async fn post_organization_collections(
CollectionUser::save(
&org_user.user_uuid,
&collection.uuid,
- user.ReadOnly,
- user.HidePasswords,
+ user.read_only,
+ user.hide_passwords,
&conn,
)
.await?;
@@ -368,7 +367,7 @@ async fn put_organization_collection_update(
org_id: &str,
col_id: &str,
headers: ManagerHeaders,
- data: JsonUpcase<NewCollectionData>,
+ data: Json<NewCollectionData>,
conn: DbConn,
) -> JsonResult {
post_organization_collection_update(org_id, col_id, headers, data, conn).await
@@ -379,10 +378,10 @@ async fn post_organization_collection_update(
org_id: &str,
col_id: &str,
_headers: ManagerHeaders,
- data: JsonUpcase<NewCollectionData>,
+ data: Json<NewCollectionData>,
conn: DbConn,
) -> JsonResult {
- let data: NewCollectionData = data.into_inner().data;
+ let data: NewCollectionData = data.into_inner();
let Some(org) = Organization::find_by_uuid(org_id, &conn).await else {
err!("Can't find organization details")
};
@@ -392,15 +391,15 @@ async fn post_organization_collection_update(
if collection.org_uuid != org.uuid {
err!("Collection is not owned by organization");
}
- collection.name = data.Name;
- collection.external_id = match data.ExternalId {
+ collection.name = data.name;
+ collection.external_id = match data.external_id {
Some(external_id) if !external_id.trim().is_empty() => Some(external_id),
_ => None,
};
collection.save(&conn).await?;
CollectionUser::delete_all_by_collection(col_id, &conn).await?;
- for user in data.Users {
- let Some(org_user) = UserOrganization::find_by_uuid(&user.Id, &conn).await else {
+ for user in data.users {
+ let Some(org_user) = UserOrganization::find_by_uuid(&user.id, &conn).await else {
err!("User is not part of organization")
};
if org_user.access_all {
@@ -409,8 +408,8 @@ async fn post_organization_collection_update(
CollectionUser::save(
&org_user.user_uuid,
col_id,
- user.ReadOnly,
- user.HidePasswords,
+ user.read_only,
+ user.hide_passwords,
&conn,
)
.await?;
@@ -493,10 +492,12 @@ async fn delete_organization_collection(
}
#[derive(Deserialize)]
-#[allow(non_snake_case, dead_code)]
+#[serde(rename_all = "camelCase")]
struct DeleteCollectionData {
- Id: String,
- OrgId: String,
+ #[allow(dead_code)]
+ id: String,
+ #[allow(dead_code)]
+ org_id: String,
}
#[post(
@@ -507,31 +508,31 @@ async fn post_organization_collection_delete(
org_id: &str,
col_id: &str,
headers: ManagerHeaders,
- _data: JsonUpcase<DeleteCollectionData>,
+ _data: Json<DeleteCollectionData>,
conn: DbConn,
) -> EmptyResult {
_delete_organization_collection(org_id, col_id, &headers, &conn).await
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct BulkCollectionIds {
- Ids: Vec<String>,
- OrganizationId: String,
+ ids: Vec<String>,
+ organization_id: String,
}
#[delete("/organizations/<org_id>/collections", data = "<data>")]
async fn bulk_delete_organization_collections(
org_id: &str,
headers: ManagerHeadersLoose,
- data: JsonUpcase<BulkCollectionIds>,
+ data: Json<BulkCollectionIds>,
conn: DbConn,
) -> EmptyResult {
- let data: BulkCollectionIds = data.into_inner().data;
- if org_id != data.OrganizationId {
+ let data: BulkCollectionIds = data.into_inner();
+ if org_id != data.organization_id {
err!("OrganizationId mismatch");
}
- let collections = data.Ids;
+ let collections = data.ids;
let headers = ManagerHeaders::from_loose(headers, &collections, &conn).await?;
for col_id in collections {
_delete_organization_collection(org_id, &col_id, &headers, &conn).await?;
@@ -558,7 +559,6 @@ async fn get_org_collection_detail(
err!("User is not part of organization")
};
let groups: Vec<Value> = Vec::new();
- let mut assigned = false;
let users: Vec<Value> =
CollectionUser::find_by_collection_swap_user_uuid_with_org_user_uuid(
&collection.uuid,
@@ -567,24 +567,17 @@ async fn get_org_collection_detail(
.await
.iter()
.map(|collection_user| {
- // Remember `user_uuid` is swapped here with the `user_org.uuid` with a join during the `find_by_collection_swap_user_uuid_with_org_user_uuid` call.
- // We check here if the current user is assigned to this collection or not.
- if collection_user.user_uuid == user_org.uuid {
- assigned = true;
- }
SelectionReadOnly::to_collection_user_details_read_only(collection_user)
.to_json()
})
.collect();
-
- if user_org.access_all {
- assigned = true;
- }
+ let assigned =
+ Collection::can_access_collection(&user_org, &collection.uuid, &conn).await;
let mut json_object = collection.to_json();
- json_object["Assigned"] = json!(assigned);
- json_object["Users"] = json!(users);
- json_object["Groups"] = json!(groups);
- json_object["Object"] = json!("collectionAccessDetails");
+ json_object["assigned"] = json!(assigned);
+ json_object["users"] = json!(users);
+ json_object["groups"] = json!(groups);
+ json_object["object"] = json!("collectionAccessDetails");
Ok(Json(json_object))
}
}
@@ -618,7 +611,7 @@ async fn get_collection_users(
async fn put_collection_users(
org_id: &str,
coll_id: &str,
- data: JsonUpcaseVec<CollectionData>,
+ data: Json<Vec<CollectionData>>,
_headers: ManagerHeaders,
conn: DbConn,
) -> EmptyResult {
@@ -632,14 +625,21 @@ async fn put_collection_users(
// Delete all the user-collections
CollectionUser::delete_all_by_collection(coll_id, &conn).await?;
// And then add all the received ones (except if the user has access_all)
- for d in data.iter().map(|d| &d.data) {
- let Some(user) = UserOrganization::find_by_uuid(&d.Id, &conn).await else {
+ for d in data.iter() {
+ let Some(user) = UserOrganization::find_by_uuid(&d.id, &conn).await else {
err!("User is not part of organization")
};
if user.access_all {
continue;
}
- CollectionUser::save(&user.user_uuid, coll_id, d.ReadOnly, d.HidePasswords, &conn).await?;
+ CollectionUser::save(
+ &user.user_uuid,
+ coll_id,
+ d.read_only,
+ d.hide_passwords,
+ &conn,
+ )
+ .await?;
}
Ok(())
}
@@ -651,12 +651,23 @@ struct OrgIdData {
}
#[get("/ciphers/organization-details?<data..>")]
-async fn get_org_details(data: OrgIdData, headers: Headers, conn: DbConn) -> Json<Value> {
- Json(json!({
- "Data": _get_org_details(&data.organization_id, &headers.user.uuid, &conn).await,
- "Object": "list",
- "ContinuationToken": null,
- }))
+async fn get_org_details(data: OrgIdData, headers: Headers, conn: DbConn) -> JsonResult {
+ if UserOrganization::find_confirmed_by_user_and_org(
+ &headers.user.uuid,
+ &data.organization_id,
+ &conn,
+ )
+ .await
+ .is_none()
+ {
+ err_code!("Resource not found.", Status::NotFound.code);
+ }
+
+ Ok(Json(json!({
+ "data": _get_org_details(&data.organization_id, &headers.user.uuid, &conn).await,
+ "object": "list",
+ "continuationToken": null,
+ })))
}
async fn _get_org_details(org_id: &str, user_uuid: &str, conn: &DbConn) -> Value {
@@ -700,20 +711,20 @@ async fn get_org_users(
);
}
Json(json!({
- "Data": users_json,
- "Object": "list",
- "ContinuationToken": null,
+ "data": users_json,
+ "object": "list",
+ "continuationToken": null,
}))
}
#[post("/organizations/<org_id>/keys", data = "<data>")]
async fn post_org_keys(
org_id: &str,
- data: JsonUpcase<OrgKeyData>,
+ data: Json<OrgKeyData>,
_headers: AdminHeaders,
conn: DbConn,
) -> JsonResult {
- let data: OrgKeyData = data.into_inner().data;
+ let data: OrgKeyData = data.into_inner();
let mut org = match Organization::find_by_uuid(org_id, &conn).await {
Some(organization) => {
if organization.private_key.is_some() && organization.public_key.is_some() {
@@ -723,50 +734,52 @@ async fn post_org_keys(
}
None => err!("Can't find organization details"),
};
- org.private_key = Some(data.EncryptedPrivateKey);
- org.public_key = Some(data.PublicKey);
+ org.private_key = Some(data.encrypted_private_key);
+ org.public_key = Some(data.public_key);
org.save(&conn).await?;
Ok(Json(json!({
- "Object": "organizationKeys",
- "PublicKey": org.public_key,
- "PrivateKey": org.private_key,
+ "object": "organizationKeys",
+ "publicKey": org.public_key,
+ "privateKey": org.private_key,
})))
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct CollectionData {
- Id: String,
- ReadOnly: bool,
- HidePasswords: bool,
+ id: String,
+ read_only: bool,
+ hide_passwords: bool,
}
#[derive(Deserialize)]
-#[allow(dead_code, non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct InviteData {
- Emails: Vec<String>,
- Groups: Vec<String>,
- Type: NumberOrString,
- Collections: Option<Vec<CollectionData>>,
- AccessAll: Option<bool>,
+ #[allow(dead_code)]
+ emails: Vec<String>,
+ #[allow(dead_code)]
+ groups: Vec<String>,
+ #[allow(dead_code)]
+ r#type: NumberOrString,
+ #[allow(dead_code)]
+ collections: Option<Vec<CollectionData>>,
+ #[allow(dead_code)]
+ #[serde(default)]
+ access_all: bool,
}
const INVITATIONS_NOT_ALLOWED_MSG: &str = "Invitations are not allowed.";
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/organizations/<org_id>/users/invite", data = "<data>")]
-fn send_invite(org_id: &str, data: JsonUpcase<InviteData>, _headers: AdminHeaders) -> Error {
+fn send_invite(org_id: &str, data: Json<InviteData>, _headers: AdminHeaders) -> Error {
Error::new(INVITATIONS_NOT_ALLOWED_MSG, INVITATIONS_NOT_ALLOWED_MSG)
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/organizations/<org_id>/users/reinvite", data = "<data>")]
-fn bulk_reinvite_user(
- org_id: &str,
- data: JsonUpcase<OrgBulkIds>,
- _headers: AdminHeaders,
-) -> Json<Value> {
+fn bulk_reinvite_user(org_id: &str, data: Json<OrgBulkIds>, _headers: AdminHeaders) -> Json<Value> {
Json(json!({
- "Data": Vec::<Value>::new(),
- "Object": "list",
- "ContinuationToken": null
+ "data": Vec::<Value>::new(),
+ "object": "list",
+ "continuationToken": null
}))
}
@@ -777,29 +790,27 @@ fn reinvite_user(org_id: &str, user_org: &str, _headers: AdminHeaders) -> Error
}
#[derive(Deserialize)]
-#[allow(dead_code, non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct AcceptData {
- Token: String,
- ResetPasswordKey: Option<String>,
+ #[allow(dead_code)]
+ token: String,
+ #[allow(dead_code)]
+ reset_password_key: Option<String>,
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/organizations/<org_id>/users/<_org_user_id>/accept", data = "<data>")]
-fn accept_invite(org_id: &str, _org_user_id: &str, data: JsonUpcase<AcceptData>) -> Error {
+fn accept_invite(org_id: &str, _org_user_id: &str, data: Json<AcceptData>) -> Error {
Error::new(INVITATIONS_NOT_ALLOWED_MSG, INVITATIONS_NOT_ALLOWED_MSG)
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/organizations/<org_id>/users/confirm", data = "<data>")]
-fn bulk_confirm_invite(
- org_id: &str,
- data: JsonUpcase<Value>,
- _headers: AdminHeaders,
-) -> Json<Value> {
+fn bulk_confirm_invite(org_id: &str, data: Json<Value>, _headers: AdminHeaders) -> Json<Value> {
Json(json!({
- "Data": Vec::<Value>::new(),
- "Object": "list",
- "ContinuationToken": null
+ "data": Vec::<Value>::new(),
+ "object": "list",
+ "continuationToken": null
}))
}
@@ -808,7 +819,7 @@ fn bulk_confirm_invite(
fn confirm_invite(
org_id: &str,
org_user_id: &str,
- data: JsonUpcase<Value>,
+ data: Json<Value>,
_headers: AdminHeaders,
) -> Error {
Error::new(INVITATIONS_NOT_ALLOWED_MSG, INVITATIONS_NOT_ALLOWED_MSG)
@@ -836,12 +847,13 @@ async fn get_user(
}
#[derive(Deserialize)]
-#[allow(dead_code, non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct EditUserData {
- Type: NumberOrString,
- Collections: Option<Vec<CollectionData>>,
- Groups: Option<Vec<String>>,
- AccessAll: bool,
+ r#type: NumberOrString,
+ collections: Option<Vec<CollectionData>>,
+ #[allow(dead_code)]
+ groups: Option<Vec<String>>,
+ access_all: bool,
}
#[put(
@@ -852,7 +864,7 @@ struct EditUserData {
async fn put_organization_user(
org_id: &str,
org_user_id: &str,
- data: JsonUpcase<EditUserData>,
+ data: Json<EditUserData>,
headers: AdminHeaders,
conn: DbConn,
) -> EmptyResult {
@@ -867,12 +879,12 @@ async fn put_organization_user(
async fn edit_user(
org_id: &str,
org_user_id: &str,
- data: JsonUpcase<EditUserData>,
+ data: Json<EditUserData>,
headers: AdminHeaders,
conn: DbConn,
) -> EmptyResult {
- let data: EditUserData = data.into_inner().data;
- let Some(new_type) = UserOrgType::from_str(&data.Type.into_string()) else {
+ let data: EditUserData = data.into_inner();
+ let Some(new_type) = UserOrgType::from_str(&data.r#type.into_string()) else {
err!("Invalid type")
};
let Some(mut user_to_edit) =
@@ -914,7 +926,7 @@ async fn edit_user(
}
}
}
- user_to_edit.access_all = data.AccessAll;
+ user_to_edit.access_all = data.access_all;
user_to_edit.atype = i32::from(new_type);
// Delete all the odd collections
for c in
@@ -924,16 +936,16 @@ async fn edit_user(
c.delete(&conn).await?;
}
// If no accessAll, add the collections received
- if !data.AccessAll {
- for col in data.Collections.iter().flatten() {
- match Collection::find_by_uuid_and_org(&col.Id, org_id, &conn).await {
+ if !data.access_all {
+ for col in data.collections.iter().flatten() {
+ match Collection::find_by_uuid_and_org(&col.id, org_id, &conn).await {
None => err!("Collection not found in Organization"),
Some(collection) => {
CollectionUser::save(
&user_to_edit.user_uuid,
&collection.uuid,
- col.ReadOnly,
- col.HidePasswords,
+ col.read_only,
+ col.hide_passwords,
&conn,
)
.await?;
@@ -947,29 +959,29 @@ async fn edit_user(
#[delete("/organizations/<org_id>/users", data = "<data>")]
async fn bulk_delete_user(
org_id: &str,
- data: JsonUpcase<OrgBulkIds>,
+ data: Json<OrgBulkIds>,
headers: AdminHeaders,
conn: DbConn,
) -> Json<Value> {
- let data: OrgBulkIds = data.into_inner().data;
+ let data: OrgBulkIds = data.into_inner();
let mut bulk_response = Vec::new();
- for org_user_id in data.Ids {
+ for org_user_id in data.ids {
let err_msg = match _delete_user(org_id, &org_user_id, &headers, &conn).await {
Ok(()) => String::new(),
Err(e) => format!("{e:?}"),
};
bulk_response.push(json!(
{
- "Object": "OrganizationBulkConfirmResponseModel",
- "Id": org_user_id,
- "Error": err_msg
+ "object": "organizationBulkConfirmResponseModel",
+ "id": org_user_id,
+ "error": err_msg
}
));
}
Json(json!({
- "Data": bulk_response,
- "Object": "list",
- "ContinuationToken": null
+ "data": bulk_response,
+ "object": "list",
+ "continuationToken": null
}))
}
@@ -1023,26 +1035,26 @@ async fn _delete_user(
#[post("/organizations/<org_id>/users/public-keys", data = "<data>")]
async fn bulk_public_keys(
org_id: &str,
- data: JsonUpcase<OrgBulkIds>,
+ data: Json<OrgBulkIds>,
_headers: AdminHeaders,
conn: DbConn,
) -> Json<Value> {
- let data: OrgBulkIds = data.into_inner().data;
+ let data: OrgBulkIds = data.into_inner();
let mut bulk_response = Vec::new();
// Check all received UserOrg UUID's and find the matching User to retrieve the public-key.
// If the user does not exists, just ignore it, and do not return any information regarding that UserOrg UUID.
// The web-vault will then ignore that user for the following steps.
- for user_org_id in data.Ids {
+ for user_org_id in data.ids {
if let Some(user_org) =
UserOrganization::find_by_uuid_and_org(&user_org_id, org_id, &conn).await
{
if let Some(user) = User::find_by_uuid(&user_org.user_uuid, &conn).await {
bulk_response.push(json!(
{
- "Object": "organizationUserPublicKeyResponseModel",
- "Id": user_org_id,
- "UserId": user.uuid,
- "Key": user.public_key
+ "object": "organizationUserPublicKeyResponseModel",
+ "id": user_org_id,
+ "userId": user.uuid,
+ "key": user.public_key
}
));
} else {
@@ -1053,9 +1065,9 @@ async fn bulk_public_keys(
}
}
Json(json!({
- "Data": bulk_response,
- "Object": "list",
- "ContinuationToken": null
+ "data": bulk_response,
+ "object": "list",
+ "continuationToken": null
}))
}
@@ -1063,39 +1075,39 @@ use super::ciphers::update_cipher_from_data;
use super::ciphers::CipherData;
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct ImportData {
- Ciphers: Vec<CipherData>,
- Collections: Vec<NewCollectionData>,
- CollectionRelationships: Vec<RelationsData>,
+ ciphers: Vec<CipherData>,
+ collections: Vec<NewCollectionData>,
+ collection_relationships: Vec<RelationsData>,
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct RelationsData {
// Cipher index
- Key: usize,
+ key: usize,
// Collection index
- Value: usize,
+ value: usize,
}
#[post("/ciphers/import-organization?<query..>", data = "<data>")]
async fn post_org_import(
query: OrgIdData,
- data: JsonUpcase<ImportData>,
+ data: Json<ImportData>,
headers: AdminHeaders,
conn: DbConn,
) -> EmptyResult {
- let data: ImportData = data.into_inner().data;
+ let data: ImportData = data.into_inner();
let org_id = query.organization_id;
// Validate the import before continuing
// Bitwarden does not process the import if there is one item invalid.
// Since we check for the size of the encrypted note length, we need to do that here to pre-validate it.
// TODO: See if we can optimize the whole cipher adding/importing and prevent duplicate code and checks.
- Cipher::validate_notes(&data.Ciphers)?;
+ Cipher::validate_notes(&data.ciphers)?;
let mut collections = Vec::new();
- for coll in data.Collections {
- let collection = Collection::new(org_id.clone(), coll.Name, coll.ExternalId);
+ for coll in data.collections {
+ let collection = Collection::new(org_id.clone(), coll.name, coll.external_id);
if collection.save(&conn).await.is_err() {
collections.push(Err(Error::new(
"Failed to create Collection",
@@ -1107,14 +1119,14 @@ async fn post_org_import(
}
// Read the relations between collections and ciphers
let mut relations = Vec::new();
- for relation in data.CollectionRelationships {
- relations.push((relation.Key, relation.Value));
+ for relation in data.collection_relationships {
+ relations.push((relation.key, relation.value));
}
let headers: Headers = headers.into();
let mut ciphers = Vec::new();
- for cipher_data in data.Ciphers {
- let mut cipher = Cipher::new(cipher_data.Type, cipher_data.Name.clone());
- update_cipher_from_data(&mut cipher, cipher_data, &headers, false, &conn, true)
+ for cipher_data in data.ciphers {
+ let mut cipher = Cipher::new(cipher_data.r#type, cipher_data.name.clone());
+ update_cipher_from_data(&mut cipher, cipher_data, &headers, None, &conn, true)
.await
.ok();
ciphers.push(cipher);
@@ -1134,19 +1146,102 @@ async fn post_org_import(
user.update_revision(&conn).await
}
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct BulkCollectionsData {
+ organization_id: String,
+ cipher_ids: Vec<String>,
+ collection_ids: HashSet<String>,
+ remove_collections: bool,
+}
+
+// This endpoint is only reachable via the organization view, therefor this endpoint is located here
+// Also Bitwarden does not send out Notifications for these changes, it only does this for individual cipher collection updates
+#[allow(clippy::iter_over_hash_type)]
+#[post("/ciphers/bulk-collections", data = "<data>")]
+async fn post_bulk_collections(
+ data: Json<BulkCollectionsData>,
+ headers: Headers,
+ conn: DbConn,
+) -> EmptyResult {
+ let data: BulkCollectionsData = data.into_inner();
+
+ // This feature does not seem to be active on all the clients
+ // To prevent future issues, add a check to block a call when this is set to true
+ if data.remove_collections {
+ err!("Bulk removing of collections is not yet implemented")
+ }
+
+ // Get all the collection available to the user in one query
+ // Also filter based upon the provided collections
+ let user_collections: HashMap<String, Collection> =
+ Collection::find_by_organization_and_user_uuid(
+ &data.organization_id,
+ &headers.user.uuid,
+ &conn,
+ )
+ .await
+ .into_iter()
+ .filter_map(|c| {
+ if data.collection_ids.contains(&c.uuid) {
+ Some((c.uuid.clone(), c))
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ // Verify if all the collections requested exists and are writeable for the user, else abort
+ for collection_uuid in &data.collection_ids {
+ match user_collections.get(collection_uuid) {
+ Some(collection)
+ if collection
+ .is_writable_by_user(&headers.user.uuid, &conn)
+ .await => {}
+ _ => err_code!(
+ "Resource not found",
+ "User does not have access to a collection",
+ 404
+ ),
+ }
+ }
+
+ for cipher_id in &data.cipher_ids {
+ // Only act on existing cipher uuid's
+ // Do not abort the operation just ignore it, it could be a cipher was just deleted for example
+ if let Some(cipher) =
+ Cipher::find_by_uuid_and_org(cipher_id, &data.organization_id, &conn).await
+ {
+ if cipher
+ .is_write_accessible_to_user(&headers.user.uuid, &conn)
+ .await
+ {
+ for collection in &data.collection_ids {
+ CollectionCipher::save(&cipher.uuid, collection, &conn).await?;
+ }
+ }
+ };
+ }
+
+ Ok(())
+}
+
#[get("/organizations/<org_id>/policies")]
async fn list_policies(org_id: &str, _headers: AdminHeaders, conn: DbConn) -> Json<Value> {
let policies = OrgPolicy::find_by_org(org_id, &conn).await;
let policies_json: Vec<Value> = policies.iter().map(OrgPolicy::to_json).collect();
Json(json!({
- "Data": policies_json,
- "Object": "list",
- "ContinuationToken": null
+ "data": policies_json,
+ "object": "list",
+ "continuationToken": null
}))
}
#[get("/organizations/<org_id>/policies/token?<token>")]
async fn list_policies_token(org_id: &str, token: &str, conn: DbConn) -> JsonResult {
+ if org_id == "undefined" && token == "undefined" {
+ return Ok(Json(json!({})));
+ }
let invite = auth::decode_invite(token)?;
let Some(invite_org_id) = invite.org_id else {
err!("Invalid token")
@@ -1158,9 +1253,9 @@ async fn list_policies_token(org_id: &str, token: &str, conn: DbConn) -> JsonRes
let policies = OrgPolicy::find_by_org(org_id, &conn).await;
let policies_json: Vec<Value> = policies.iter().map(OrgPolicy::to_json).collect();
Ok(Json(json!({
- "Data": policies_json,
- "Object": "list",
- "ContinuationToken": null
+ "data": policies_json,
+ "object": "list",
+ "continuationToken": null
})))
}
@@ -1256,25 +1351,25 @@ fn get_organization_tax(org_id: &str, _headers: Headers) -> Json<Value> {
fn get_plans() -> Json<Value> {
// Respond with a minimal json just enough to allow the creation of an new organization.
Json(json!({
- "Object": "list",
- "Data": [{
- "Object": "plan",
- "Type": 0i32,
- "Product": 0i32,
- "Name": "Free",
- "NameLocalizationKey": "planNameFree",
- "BitwardenProduct": 0i32,
- "MaxUsers": 0i32,
- "DescriptionLocalizationKey": "planDescFree"
+ "object": "list",
+ "data": [{
+ "object": "plan",
+ "type": 0i32,
+ "product": 0i32,
+ "name": "Free",
+ "nameLocalizationKey": "planNameFree",
+ "bitwardenProduct": 0i32,
+ "maxUsers": 0i32,
+ "descriptionLocalizationKey": "planDescFree"
},{
- "Object": "plan",
- "Type": 0i32,
- "Product": 1i32,
- "Name": "Free",
- "NameLocalizationKey": "planNameFree",
- "BitwardenProduct": 1i32,
- "MaxUsers": 0i32,
- "DescriptionLocalizationKey": "planDescFree"
+ "object": "plan",
+ "type": 0i32,
+ "product": 1i32,
+ "name": "Free",
+ "nameLocalizationKey": "planNameFree",
+ "bitwardenProduct": 1i32,
+ "maxUsers": 0i32,
+ "descriptionLocalizationKey": "planDescFree"
}],
"ContinuationToken": null
}))
@@ -1294,46 +1389,49 @@ fn get_plans_tax_rates(_headers: Headers) -> Json<Value> {
fn _empty_data_json() -> Value {
json!({
- "Object": "list",
- "Data": [],
- "ContinuationToken": null
+ "object": "list",
+ "data": [],
+ "continuationToken": null
})
}
#[derive(Deserialize)]
-#[allow(non_snake_case, dead_code)]
+#[serde(rename_all = "camelCase")]
struct OrgImportGroupData {
- Name: String, // "GroupName"
- ExternalId: String, // "cn=GroupName,ou=Groups,dc=example,dc=com"
- Users: Vec<String>, // ["uid=user,ou=People,dc=example,dc=com"]
+ #[allow(dead_code)]
+ name: String, // "GroupName"
+ #[allow(dead_code)]
+ external_id: String, // "cn=GroupName,ou=Groups,dc=example,dc=com"
+ #[allow(dead_code)]
+ users: Vec<String>, // ["uid=user,ou=People,dc=example,dc=com"]
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct OrgImportUserData {
- Email: String, // "user@maildomain.net"
+ email: String, // "user@maildomain.net"
#[allow(dead_code)]
- ExternalId: String, // "uid=user,ou=People,dc=example,dc=com"
- Deleted: bool,
+ external_id: String, // "uid=user,ou=People,dc=example,dc=com"
+ deleted: bool,
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct OrgImportData {
#[allow(dead_code)]
- Groups: Vec<OrgImportGroupData>,
- OverwriteExisting: bool,
- Users: Vec<OrgImportUserData>,
+ groups: Vec<OrgImportGroupData>,
+ overwrite_existing: bool,
+ users: Vec<OrgImportUserData>,
}
#[post("/organizations/<org_id>/import", data = "<data>")]
async fn import(
org_id: &str,
- data: JsonUpcase<OrgImportData>,
+ data: Json<OrgImportData>,
headers: Headers,
conn: DbConn,
) -> EmptyResult {
- let data = data.into_inner().data;
+ let data = data.into_inner();
// TODO: Currently we aren't storing the externalId's anywhere, so we also don't have a way
// to differentiate between auto-imported users and manually added ones.
// This means that this endpoint can end up removing users that were added manually by an admin,
@@ -1344,21 +1442,21 @@ async fn import(
Some(_) => err!("User has insufficient permissions to use Directory Connector"),
None => err!("User not part of organization"),
};
- for user_data in &data.Users {
- if user_data.Deleted {
+ for user_data in &data.users {
+ if user_data.deleted {
// If user is marked for deletion and it exists, delete it
if let Some(user_org) =
- UserOrganization::find_by_email_and_org(&user_data.Email, org_id, &conn).await
+ UserOrganization::find_by_email_and_org(&user_data.email, org_id, &conn).await
{
user_org.delete(&conn).await?;
}
// If user is not part of the organization, but it exists
- } else if UserOrganization::find_by_email_and_org(&user_data.Email, org_id, &conn)
+ } else if UserOrganization::find_by_email_and_org(&user_data.email, org_id, &conn)
.await
.is_none()
{
- if let Some(user) = User::find_by_mail(&user_data.Email, &conn).await {
+ if let Some(user) = User::find_by_mail(&user_data.email, &conn).await {
let user_org_status = i32::from(UserOrgStatus::Accepted);
let mut new_org_user =
UserOrganization::new(user.uuid.clone(), String::from(org_id));
@@ -1372,7 +1470,7 @@ async fn import(
}
}
// If this flag is enabled, any user that isn't provided in the Users list will be removed (by default they will be kept unless they have Deleted == true)
- if data.OverwriteExisting {
+ if data.overwrite_existing {
for user_org in
UserOrganization::find_by_org_and_type(org_id, UserOrgType::User, &conn).await
{
@@ -1380,7 +1478,7 @@ async fn import(
.await
.map(|u| u.email)
{
- if !data.Users.iter().any(|u| u.Email == user_email) {
+ if !data.users.iter().any(|u| u.email == user_email) {
user_org.delete(&conn).await?;
}
}
@@ -1404,7 +1502,7 @@ async fn deactivate_organization_user(
#[put("/organizations/<org_id>/users/deactivate", data = "<data>")]
async fn bulk_deactivate_organization_user(
org_id: &str,
- data: JsonUpcase<Value>,
+ data: Json<Value>,
headers: AdminHeaders,
conn: DbConn,
) -> Json<Value> {
@@ -1424,11 +1522,11 @@ async fn revoke_organization_user(
#[put("/organizations/<org_id>/users/revoke", data = "<data>")]
async fn bulk_revoke_organization_user(
org_id: &str,
- data: JsonUpcase<Value>,
+ data: Json<Value>,
headers: AdminHeaders,
conn: DbConn,
) -> Json<Value> {
- let data = data.into_inner().data;
+ let data = data.into_inner();
let mut bulk_response = Vec::new();
match data["Ids"].as_array() {
Some(org_users) => {
@@ -1441,9 +1539,9 @@ async fn bulk_revoke_organization_user(
};
bulk_response.push(json!(
{
- "Object": "OrganizationUserBulkResponseModel",
- "Id": org_user_id,
- "Error": err_msg
+ "object": "organizationUserBulkResponseModel",
+ "id": org_user_id,
+ "error": err_msg
}
));
}
@@ -1451,9 +1549,9 @@ async fn bulk_revoke_organization_user(
None => panic!("No users to revoke"),
}
Json(json!({
- "Data": bulk_response,
- "Object": "list",
- "ContinuationToken": null
+ "data": bulk_response,
+ "object": "list",
+ "continuationToken": null
}))
}
@@ -1506,7 +1604,7 @@ async fn activate_organization_user(
#[put("/organizations/<org_id>/users/activate", data = "<data>")]
async fn bulk_activate_organization_user(
org_id: &str,
- data: JsonUpcase<Value>,
+ data: Json<OrgBulkIds>,
headers: AdminHeaders,
conn: DbConn,
) -> Json<Value> {
@@ -1526,36 +1624,30 @@ async fn restore_organization_user(
#[put("/organizations/<org_id>/users/restore", data = "<data>")]
async fn bulk_restore_organization_user(
org_id: &str,
- data: JsonUpcase<Value>,
+ data: Json<OrgBulkIds>,
headers: AdminHeaders,
conn: DbConn,
) -> Json<Value> {
- let data = data.into_inner().data;
+ let data = data.into_inner();
let mut bulk_response = Vec::new();
- match data["Ids"].as_array() {
- Some(org_users) => {
- for org_user_id in org_users {
- let org_user_id = org_user_id.as_str().unwrap_or_default();
- let err_msg =
- match _restore_organization_user(org_id, org_user_id, &headers, &conn).await {
- Ok(()) => String::new(),
- Err(e) => format!("{e:?}"),
- };
- bulk_response.push(json!(
- {
- "Object": "OrganizationUserBulkResponseModel",
- "Id": org_user_id,
- "Error": err_msg
- }
- ));
+ for org_user_id in data.ids {
+ let err_msg = match _restore_organization_user(org_id, &org_user_id, &headers, &conn).await
+ {
+ Ok(()) => String::new(),
+ Err(e) => format!("{e:?}"),
+ };
+ bulk_response.push(json!(
+ {
+ "object": "OrganizationUserBulkResponseModel",
+ "id": org_user_id,
+ "error": err_msg
}
- }
- None => panic!("No users to restore"),
+ ));
}
Json(json!({
- "Data": bulk_response,
- "Object": "list",
- "ContinuationToken": null
+ "data": bulk_response,
+ "object": "list",
+ "continuationToken": null
}))
}
@@ -1599,29 +1691,29 @@ async fn _restore_organization_user(
#[get("/organizations/<org_id>/groups")]
fn get_groups(org_id: &str, _headers: ManagerHeadersLoose) -> Json<Value> {
Json(json!({
- "Data": Vec::<Value>::new(),
- "Object": "list",
- "ContinuationToken": null,
+ "data": Vec::<Value>::new(),
+ "object": "list",
+ "continuationToken": null,
}))
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct GroupRequest;
#[derive(Serialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct SelectionReadOnly {
- Id: String,
- ReadOnly: bool,
- HidePasswords: bool,
+ id: String,
+ read_only: bool,
+ hide_passwords: bool,
}
impl SelectionReadOnly {
fn to_collection_user_details_read_only(collection_user: &CollectionUser) -> Self {
Self {
- Id: collection_user.user_uuid.clone(),
- ReadOnly: collection_user.read_only,
- HidePasswords: collection_user.hide_passwords,
+ id: collection_user.user_uuid.clone(),
+ read_only: collection_user.read_only,
+ hide_passwords: collection_user.hide_passwords,
}
}
fn to_json(&self) -> Value {
@@ -1635,7 +1727,7 @@ const GROUPS_DISABLED_MSG: &str = "Groups are disabled.";
fn post_group(
org_id: &str,
group_id: &str,
- data: JsonUpcase<GroupRequest>,
+ data: Json<GroupRequest>,
_headers: AdminHeaders,
) -> Error {
Error::new(GROUPS_DISABLED_MSG, GROUPS_DISABLED_MSG)
@@ -1643,7 +1735,7 @@ fn post_group(
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/organizations/<org_id>/groups", data = "<data>")]
-fn post_groups(org_id: &str, _headers: AdminHeaders, data: JsonUpcase<GroupRequest>) -> Error {
+fn post_groups(org_id: &str, _headers: AdminHeaders, data: Json<GroupRequest>) -> Error {
Error::new(GROUPS_DISABLED_MSG, GROUPS_DISABLED_MSG)
}
@@ -1652,7 +1744,7 @@ fn post_groups(org_id: &str, _headers: AdminHeaders, data: JsonUpcase<GroupReque
fn put_group(
org_id: &str,
group_id: &str,
- data: JsonUpcase<GroupRequest>,
+ data: Json<GroupRequest>,
_headers: AdminHeaders,
) -> Error {
Error::new(GROUPS_DISABLED_MSG, GROUPS_DISABLED_MSG)
@@ -1677,7 +1769,7 @@ fn delete_group(org_id: &str, group_id: &str, _headers: AdminHeaders) -> Error {
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[delete("/organizations/<org_id>/groups", data = "<data>")]
-fn bulk_delete_groups(org_id: &str, data: JsonUpcase<OrgBulkIds>, _headers: AdminHeaders) -> Error {
+fn bulk_delete_groups(org_id: &str, data: Json<OrgBulkIds>, _headers: AdminHeaders) -> Error {
Error::new(GROUPS_DISABLED_MSG, GROUPS_DISABLED_MSG)
}
@@ -1698,7 +1790,7 @@ fn put_group_users(
org_id: &str,
group_id: &str,
_headers: AdminHeaders,
- data: JsonVec<String>,
+ data: Json<Vec<String>>,
) -> Error {
Error::new(GROUPS_DISABLED_MSG, GROUPS_DISABLED_MSG)
}
@@ -1710,7 +1802,7 @@ fn get_user_groups(_org_id: &str, user_id: &str, _headers: AdminHeaders) -> Erro
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct OrganizationUserUpdateGroupsRequest;
#[allow(unused_variables, clippy::needless_pass_by_value)]
@@ -1718,7 +1810,7 @@ struct OrganizationUserUpdateGroupsRequest;
fn post_user_groups(
org_id: &str,
org_user_id: &str,
- data: JsonUpcase<OrganizationUserUpdateGroupsRequest>,
+ data: Json<OrganizationUserUpdateGroupsRequest>,
_headers: AdminHeaders,
) -> Error {
Error::new(GROUPS_DISABLED_MSG, GROUPS_DISABLED_MSG)
@@ -1729,7 +1821,7 @@ fn post_user_groups(
fn put_user_groups(
org_id: &str,
org_user_id: &str,
- data: JsonUpcase<OrganizationUserUpdateGroupsRequest>,
+ data: Json<OrganizationUserUpdateGroupsRequest>,
_headers: AdminHeaders,
) -> Error {
Error::new(GROUPS_DISABLED_MSG, GROUPS_DISABLED_MSG)
@@ -1758,28 +1850,33 @@ fn delete_group_user(
}
#[derive(Deserialize)]
-#[allow(dead_code, non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct OrganizationUserResetPasswordEnrollmentRequest {
- ResetPasswordKey: Option<String>,
- MasterPasswordHash: Option<String>,
+ #[allow(dead_code)]
+ reset_password_key: Option<String>,
+ #[allow(dead_code)]
+ master_password_hash: Option<String>,
+ #[allow(dead_code)]
+ otp: Option<String>,
}
#[derive(Deserialize)]
-#[allow(dead_code, non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct OrganizationUserResetPasswordRequest {
- NewMasterPasswordHash: String,
- Key: String,
+ #[allow(dead_code)]
+ new_master_password_hash: String,
+ #[allow(dead_code)]
+ key: String,
}
-
-#[get("/organizations/<org_id>/keys")]
-async fn get_organization_keys(org_id: &str, conn: DbConn) -> JsonResult {
+#[get("/organizations/<org_id>/public-key")]
+async fn get_organization_public_key(org_id: &str, _headers: Headers, conn: DbConn) -> JsonResult {
let Some(org) = Organization::find_by_uuid(org_id, &conn).await else {
err!("Organization not found")
};
+
Ok(Json(json!({
- "Object": "organizationKeys",
- "PublicKey": org.public_key,
- "PrivateKey": org.private_key,
+ "object": "organizationPublicKey",
+ "publicKey": org.public_key,
})))
}
const PASS_RESET_MSG: &str = "Password reset is not supported on an email-disabled instance.";
@@ -1792,7 +1889,7 @@ async fn put_reset_password(
org_id: &str,
org_user_id: &str,
_headers: AdminHeaders,
- data: JsonUpcase<OrganizationUserResetPasswordRequest>,
+ data: Json<OrganizationUserResetPasswordRequest>,
conn: DbConn,
) -> EmptyResult {
let Some(org) = Organization::find_by_uuid(org_id, &conn).await else {
@@ -1824,7 +1921,7 @@ fn put_reset_password_enrollment(
org_id: &str,
org_user_id: &str,
_headers: Headers,
- data: JsonUpcase<OrganizationUserResetPasswordEnrollmentRequest>,
+ data: Json<OrganizationUserResetPasswordEnrollmentRequest>,
) -> Error {
Error::new(PASS_RESET_MSG, PASS_RESET_MSG)
}
@@ -1875,15 +1972,11 @@ async fn get_org_export(org_id: &str, headers: AdminHeaders, conn: DbConn) -> Js
const API_DISABLED_MSG: &str = "API access is disabled.";
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/organizations/<org_id>/api-key", data = "<data>")]
-fn api_key(org_id: &str, data: JsonUpcase<PasswordOrOtpData>, _headers: AdminHeaders) -> Error {
+fn api_key(org_id: &str, data: Json<PasswordOrOtpData>, _headers: AdminHeaders) -> Error {
Error::new(API_DISABLED_MSG, API_DISABLED_MSG)
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/organizations/<org_id>/rotate-api-key", data = "<data>")]
-fn rotate_api_key(
- org_id: &str,
- data: JsonUpcase<PasswordOrOtpData>,
- _headers: AdminHeaders,
-) -> Error {
+fn rotate_api_key(org_id: &str, data: Json<PasswordOrOtpData>, _headers: AdminHeaders) -> Error {
Error::new(API_DISABLED_MSG, API_DISABLED_MSG)
}
diff --git a/src/api/core/public.rs b/src/api/core/public.rs
@@ -1,32 +1,37 @@
-use crate::{api::JsonUpcase, error::Error};
-use rocket::Route;
+use crate::error::Error;
+use rocket::{serde::json::Json, Route};
pub fn routes() -> Vec<Route> {
routes![ldap_import]
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct OrgImportGroupData;
#[derive(Deserialize)]
-#[allow(dead_code, non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct OrgImportUserData {
- Email: String,
- ExternalId: String,
- Deleted: bool,
+ #[allow(dead_code)]
+ email: String,
+ #[allow(dead_code)]
+ external_id: String,
+ #[allow(dead_code)]
+ deleted: bool,
}
#[derive(Deserialize)]
-#[allow(dead_code, non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct OrgImportData {
- Members: Vec<OrgImportUserData>,
- OverwriteExisting: bool,
+ #[allow(dead_code)]
+ members: Vec<OrgImportUserData>,
+ #[allow(dead_code)]
+ overwrite_existing: bool,
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/public/organization/import", data = "<data>")]
-fn ldap_import(data: JsonUpcase<OrgImportData>) -> Error {
+fn ldap_import(data: Json<OrgImportData>) -> Error {
const MSG: &str = "LDAP import is permanently disabled.";
Error::new(MSG, MSG)
}
diff --git a/src/api/core/sends.rs b/src/api/core/sends.rs
@@ -1,13 +1,14 @@
use crate::{
- api::{JsonUpcase, NumberOrString},
auth::Headers,
error::Error,
- util::{SafeString, UpCase},
+ util::{NumberOrString, SafeString},
};
use chrono::{DateTime, Utc};
-use rocket::form::Form;
-use rocket::fs::{NamedFile, TempFile};
-use rocket::serde::json::Json;
+use rocket::{
+ form::Form,
+ fs::{NamedFile, TempFile},
+ serde::json::Json,
+};
use serde_json::Value;
pub fn routes() -> Vec<rocket::Route> {
@@ -28,30 +29,45 @@ pub fn routes() -> Vec<rocket::Route> {
}
#[derive(Deserialize)]
-#[allow(dead_code, non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct SendData {
- Type: i32,
- Key: String,
- Password: Option<String>,
- MaxAccessCount: Option<NumberOrString>,
- ExpirationDate: Option<DateTime<Utc>>,
- DeletionDate: DateTime<Utc>,
- Disabled: bool,
- HideEmail: Option<bool>,
- Name: String,
- Notes: Option<String>,
- Text: Option<Value>,
- File: Option<Value>,
- FileLength: Option<NumberOrString>,
+ #[allow(dead_code)]
+ r#type: i32,
+ #[allow(dead_code)]
+ key: String,
+ #[allow(dead_code)]
+ password: Option<String>,
+ #[allow(dead_code)]
+ max_access_count: Option<NumberOrString>,
+ #[allow(dead_code)]
+ expiration_date: Option<DateTime<Utc>>,
+ #[allow(dead_code)]
+ deletion_date: DateTime<Utc>,
+ #[allow(dead_code)]
+ disabled: bool,
+ #[allow(dead_code)]
+ hide_email: Option<bool>,
+ #[allow(dead_code)]
+ name: String,
+ #[allow(dead_code)]
+ notes: Option<String>,
+ #[allow(dead_code)]
+ text: Option<Value>,
+ #[allow(dead_code)]
+ file: Option<Value>,
+ #[allow(dead_code)]
+ file_length: Option<NumberOrString>,
+ #[allow(dead_code)]
+ id: Option<String>,
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[get("/sends")]
fn get_sends(_headers: Headers) -> Json<Value> {
Json(json!({
- "Data": Vec::<Value>::new(),
- "Object": "list",
- "ContinuationToken": null
+ "data": Vec::<Value>::new(),
+ "object": "list",
+ "continuationToken": null
}))
}
const SENDS_DISABLED_MSG: &str = "Sends are disabled.";
@@ -63,14 +79,14 @@ fn get_send(uuid: &str, _headers: Headers) -> Error {
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/sends", data = "<data>")]
-fn post_send(data: JsonUpcase<SendData>, _headers: Headers) -> Error {
+fn post_send(data: Json<SendData>, _headers: Headers) -> Error {
Error::new(SENDS_DISABLED_MSG, SENDS_DISABLED_MSG)
}
#[allow(dead_code)]
#[derive(FromForm)]
struct UploadData<'f> {
- model: Json<UpCase<SendData>>,
+ model: Json<SendData>,
data: TempFile<'f>,
}
@@ -88,7 +104,7 @@ fn post_send_file(data: Form<UploadData<'_>>, _headers: Headers) -> Error {
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/sends/file/v2", data = "<data>")]
-fn post_send_file_v2(data: JsonUpcase<SendData>, _headers: Headers) -> Error {
+fn post_send_file_v2(data: Json<SendData>, _headers: Headers) -> Error {
Error::new(SENDS_DISABLED_MSG, SENDS_DISABLED_MSG)
}
@@ -108,20 +124,21 @@ fn post_send_file_v2_data(
}
#[derive(Deserialize)]
-#[allow(dead_code, non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct SendAccessData {
- Password: Option<String>,
+ #[allow(dead_code)]
+ password: Option<String>,
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/sends/access/<access_id>", data = "<data>")]
-fn post_access(access_id: &str, data: JsonUpcase<SendAccessData>) -> Error {
+fn post_access(access_id: &str, data: Json<SendAccessData>) -> Error {
Error::new(SENDS_DISABLED_MSG, SENDS_DISABLED_MSG)
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/sends/<send_id>/access/file/<file_id>", data = "<data>")]
-fn post_access_file(send_id: &str, file_id: &str, data: JsonUpcase<SendAccessData>) -> Error {
+fn post_access_file(send_id: &str, file_id: &str, data: Json<SendAccessData>) -> Error {
Error::new(SENDS_DISABLED_MSG, SENDS_DISABLED_MSG)
}
@@ -133,7 +150,7 @@ fn download_send(send_id: SafeString, file_id: SafeString, t: &str) -> Option<Na
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[put("/sends/<id>", data = "<data>")]
-fn put_send(id: &str, data: JsonUpcase<SendData>, _headers: Headers) -> Error {
+fn put_send(id: &str, data: Json<SendData>, _headers: Headers) -> Error {
Error::new(SENDS_DISABLED_MSG, SENDS_DISABLED_MSG)
}
diff --git a/src/api/core/two_factor/authenticator.rs b/src/api/core/two_factor/authenticator.rs
@@ -1,13 +1,13 @@
use crate::{
- api::{EmptyResult, JsonResult, JsonUpcase, NumberOrString, PasswordOrOtpData},
+ api::{EmptyResult, JsonResult, PasswordOrOtpData},
auth::{ClientIp, Headers},
crypto,
db::{models::Totp, DbConn},
error::Error,
+ util::NumberOrString,
};
use data_encoding::BASE32;
-use rocket::serde::json::Json;
-use rocket::Route;
+use rocket::{serde::json::Json, Route};
pub fn routes() -> Vec<Route> {
routes![
@@ -19,11 +19,11 @@ pub fn routes() -> Vec<Route> {
#[post("/two-factor/get-authenticator", data = "<data>")]
async fn generate_authenticator(
- data: JsonUpcase<PasswordOrOtpData>,
+ data: Json<PasswordOrOtpData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
- let data: PasswordOrOtpData = data.into_inner().data;
+ let data: PasswordOrOtpData = data.into_inner();
let user = headers.user;
data.validate(&user)?;
let totp = Totp::find_by_user(&user.uuid, &conn).await?;
@@ -32,34 +32,34 @@ async fn generate_authenticator(
_ => (false, crypto::encode_random_bytes::<20>(&BASE32)),
};
Ok(Json(json!({
- "Enabled": enabled,
- "Key": key,
- "Object": "twoFactorAuthenticator"
+ "enabled": enabled,
+ "key": key,
+ "object": "twoFactorAuthenticator"
})))
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct EnableAuthenticatorData {
- Key: String,
- Token: NumberOrString,
- MasterPasswordHash: Option<String>,
- Otp: Option<String>,
+ key: String,
+ token: NumberOrString,
+ master_password_hash: Option<String>,
+ otp: Option<String>,
}
#[post("/two-factor/authenticator", data = "<data>")]
async fn activate_authenticator(
- data: JsonUpcase<EnableAuthenticatorData>,
+ data: Json<EnableAuthenticatorData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
- let data: EnableAuthenticatorData = data.into_inner().data;
- let key = data.Key;
- let token = data.Token.into_string();
+ let data: EnableAuthenticatorData = data.into_inner();
+ let key = data.key;
+ let token = data.token.into_string();
let user = headers.user;
PasswordOrOtpData {
- MasterPasswordHash: data.MasterPasswordHash,
- Otp: data.Otp,
+ master_password_hash: data.master_password_hash,
+ otp: data.otp,
}
.validate(&user)?;
// Validate key as base32 and 20 bytes length
@@ -73,15 +73,15 @@ async fn activate_authenticator(
// Validate the token provided with the key, and save new twofactor
validate_totp_code(user.uuid, &token, key.to_uppercase(), &headers.ip, &conn).await?;
Ok(Json(json!({
- "Enabled": true,
- "Key": key,
- "Object": "twoFactorAuthenticator"
+ "enabled": true,
+ "key": key,
+ "object": "twoFactorAuthenticator"
})))
}
#[put("/two-factor/authenticator", data = "<data>")]
async fn activate_authenticator_put(
- data: JsonUpcase<EnableAuthenticatorData>,
+ data: Json<EnableAuthenticatorData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
diff --git a/src/api/core/two_factor/duo.rs b/src/api/core/two_factor/duo.rs
@@ -1,9 +1,5 @@
-use crate::{
- api::{JsonUpcase, PasswordOrOtpData},
- auth::Headers,
- error::Error,
-};
-use rocket::Route;
+use crate::{api::PasswordOrOtpData, auth::Headers, error::Error};
+use rocket::{serde::json::Json, Route};
pub fn routes() -> Vec<Route> {
routes![activate_duo, activate_duo_put, get_duo]
@@ -11,28 +7,33 @@ pub fn routes() -> Vec<Route> {
const DUO_DISABLED_MSG: &str = "Duo is disabled.";
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/two-factor/get-duo", data = "<data>")]
-fn get_duo(data: JsonUpcase<PasswordOrOtpData>, _headers: Headers) -> Error {
+fn get_duo(data: Json<PasswordOrOtpData>, _headers: Headers) -> Error {
Error::new(DUO_DISABLED_MSG, DUO_DISABLED_MSG)
}
#[derive(Deserialize)]
-#[allow(non_snake_case, dead_code)]
+#[serde(rename_all = "camelCase")]
struct EnableDuoData {
- Host: String,
- SecretKey: String,
- IntegrationKey: String,
- MasterPasswordHash: Option<String>,
- Otp: Option<String>,
+ #[allow(dead_code)]
+ host: String,
+ #[allow(dead_code)]
+ secret_key: String,
+ #[allow(dead_code)]
+ integration_key: String,
+ #[allow(dead_code)]
+ master_password_hash: Option<String>,
+ #[allow(dead_code)]
+ otp: Option<String>,
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/two-factor/duo", data = "<data>")]
-fn activate_duo(data: JsonUpcase<EnableDuoData>, _headers: Headers) -> Error {
+fn activate_duo(data: Json<EnableDuoData>, _headers: Headers) -> Error {
Error::new(DUO_DISABLED_MSG, DUO_DISABLED_MSG)
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[put("/two-factor/duo", data = "<data>")]
-fn activate_duo_put(data: JsonUpcase<EnableDuoData>, _headers: Headers) -> Error {
+fn activate_duo_put(data: Json<EnableDuoData>, _headers: Headers) -> Error {
Error::new(DUO_DISABLED_MSG, DUO_DISABLED_MSG)
}
diff --git a/src/api/core/two_factor/email.rs b/src/api/core/two_factor/email.rs
@@ -1,57 +1,64 @@
-use crate::{
- api::{JsonUpcase, PasswordOrOtpData},
- auth::Headers,
- error::Error,
-};
-use rocket::Route;
+use crate::{api::PasswordOrOtpData, auth::Headers, error::Error};
+use rocket::{serde::json::Json, Route};
pub fn routes() -> Vec<Route> {
routes![email, get_email, send_email, send_email_login]
}
#[derive(Deserialize)]
-#[allow(non_snake_case, dead_code)]
+#[serde(rename_all = "camelCase")]
struct SendEmailLoginData {
- Email: String,
- MasterPasswordHash: String,
+ #[allow(dead_code)]
+ #[serde(alias = "Email")]
+ email: String,
+ #[allow(dead_code)]
+ #[serde(alias = "MasterPasswordHash")]
+ master_password_hash: String,
}
const EMAIL_DISABLED_MSG: &str = "E-mail is disabled.";
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/two-factor/send-email-login", data = "<data>")]
-fn send_email_login(data: JsonUpcase<SendEmailLoginData>) -> Error {
+fn send_email_login(data: Json<SendEmailLoginData>) -> Error {
Error::new(EMAIL_DISABLED_MSG, EMAIL_DISABLED_MSG)
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/two-factor/get-email", data = "<data>")]
-fn get_email(data: JsonUpcase<PasswordOrOtpData>, _headers: Headers) -> Error {
+fn get_email(data: Json<PasswordOrOtpData>, _headers: Headers) -> Error {
Error::new(EMAIL_DISABLED_MSG, EMAIL_DISABLED_MSG)
}
#[derive(Deserialize)]
-#[allow(non_snake_case, dead_code)]
+#[serde(rename_all = "camelCase")]
struct SendEmailData {
- Email: String,
- MasterPasswordHash: Option<String>,
- Otp: Option<String>,
+ #[allow(dead_code)]
+ email: String,
+ #[allow(dead_code)]
+ master_password_hash: Option<String>,
+ #[allow(dead_code)]
+ otp: Option<String>,
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/two-factor/send-email", data = "<data>")]
-fn send_email(data: JsonUpcase<SendEmailData>, _headers: Headers) -> Error {
+fn send_email(data: Json<SendEmailData>, _headers: Headers) -> Error {
Error::new(EMAIL_DISABLED_MSG, EMAIL_DISABLED_MSG)
}
#[derive(Deserialize)]
-#[allow(non_snake_case, dead_code)]
+#[serde(rename_all = "camelCase")]
struct EmailData {
- Email: String,
- Token: String,
- MasterPasswordHash: Option<String>,
- Otp: Option<String>,
+ #[allow(dead_code)]
+ email: String,
+ #[allow(dead_code)]
+ token: String,
+ #[allow(dead_code)]
+ master_password_hash: Option<String>,
+ #[allow(dead_code)]
+ otp: Option<String>,
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[put("/two-factor/email", data = "<data>")]
-fn email(data: JsonUpcase<EmailData>, _headers: Headers) -> Error {
+fn email(data: Json<EmailData>, _headers: Headers) -> Error {
Error::new(EMAIL_DISABLED_MSG, EMAIL_DISABLED_MSG)
}
diff --git a/src/api/core/two_factor/mod.rs b/src/api/core/two_factor/mod.rs
@@ -1,10 +1,11 @@
use crate::{
- api::{JsonResult, JsonUpcase, NumberOrString, PasswordOrOtpData},
+ api::{JsonResult, PasswordOrOtpData},
auth::{ClientHeaders, Headers},
db::{
models::{OrgPolicyType, TwoFactorType, UserOrgType, UserOrganization},
DbConn,
},
+ util::NumberOrString,
};
pub mod authenticator;
mod duo;
@@ -37,55 +38,58 @@ pub fn routes() -> Vec<Route> {
#[get("/two-factor")]
async fn get_twofactor(headers: Headers, conn: DbConn) -> Json<Value> {
Json(json!({
- "Data": TwoFactorType::get_factors(&headers.user.uuid, &conn).await.expect("unable to get two factors"),
- "Object": "list",
- "ContinuationToken": null,
+ "data": TwoFactorType::get_factors(&headers.user.uuid, &conn).await.expect("unable to get two factors"),
+ "object": "list",
+ "continuationToken": null,
}))
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/two-factor/get-recover", data = "<data>")]
-fn get_recover(data: JsonUpcase<PasswordOrOtpData>, _headers: Headers) -> JsonResult {
+fn get_recover(data: Json<PasswordOrOtpData>, _headers: Headers) -> JsonResult {
err!("recovery codes are disabled")
}
#[derive(Deserialize)]
-#[allow(non_snake_case, dead_code)]
+#[serde(rename_all = "camelCase")]
struct RecoverTwoFactor {
- MasterPasswordHash: String,
- Email: String,
- RecoveryCode: String,
+ #[allow(dead_code)]
+ master_password_hash: String,
+ #[allow(dead_code)]
+ email: String,
+ #[allow(dead_code)]
+ recovery_code: String,
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/two-factor/recover", data = "<data>")]
-fn recover(data: JsonUpcase<RecoverTwoFactor>, _client_headers: ClientHeaders) -> JsonResult {
+fn recover(data: Json<RecoverTwoFactor>, _client_headers: ClientHeaders) -> JsonResult {
err!("recovery codes are disabled")
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct DisableTwoFactorData {
- MasterPasswordHash: Option<String>,
- Otp: Option<String>,
- Type: NumberOrString,
+ master_password_hash: Option<String>,
+ otp: Option<String>,
+ r#type: NumberOrString,
}
#[post("/two-factor/disable", data = "<data>")]
async fn disable_twofactor(
- data: JsonUpcase<DisableTwoFactorData>,
+ data: Json<DisableTwoFactorData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
- let data: DisableTwoFactorData = data.into_inner().data;
+ let data: DisableTwoFactorData = data.into_inner();
let user = headers.user;
// Delete directly after a valid token has been provided
PasswordOrOtpData {
- MasterPasswordHash: data.MasterPasswordHash,
- Otp: data.Otp,
+ master_password_hash: data.master_password_hash,
+ otp: data.otp,
}
.validate(&user)?;
- let type_ = data.Type.into_i32()?;
+ let type_ = data.r#type.into_i32()?;
TwoFactorType::try_from(type_)?
.delete_by_user(&user.uuid, &conn)
.await?;
@@ -103,15 +107,15 @@ async fn disable_twofactor(
}
}
Ok(Json(json!({
- "Enabled": false,
- "Type": type_,
- "Object": "twoFactorProvider"
+ "enabled": false,
+ "type": type_,
+ "object": "twoFactorProvider"
})))
}
#[put("/two-factor/disable", data = "<data>")]
async fn disable_twofactor_put(
- data: JsonUpcase<DisableTwoFactorData>,
+ data: Json<DisableTwoFactorData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
diff --git a/src/api/core/two_factor/protected_actions.rs b/src/api/core/two_factor/protected_actions.rs
@@ -1,5 +1,5 @@
-use crate::{api::JsonUpcase, auth::Headers, error::Error};
-use rocket::Route;
+use crate::{auth::Headers, error::Error};
+use rocket::{serde::json::Json, Route};
pub fn routes() -> Vec<Route> {
routes![request_otp, verify_otp]
@@ -12,13 +12,13 @@ fn request_otp(_headers: Headers) -> Error {
}
#[derive(Deserialize, Serialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct ProtectedActionVerify {
- OTP: String,
+ otp: String,
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/accounts/verify-otp", data = "<data>")]
-fn verify_otp(data: JsonUpcase<ProtectedActionVerify>, _headers: Headers) -> Error {
+fn verify_otp(data: Json<ProtectedActionVerify>, _headers: Headers) -> Error {
Error::new(DEVICE_LOG_IN_MSG, DEVICE_LOG_IN_MSG)
}
diff --git a/src/api/core/two_factor/webauthn.rs b/src/api/core/two_factor/webauthn.rs
@@ -1,5 +1,5 @@
use crate::{
- api::{EmptyResult, JsonResult, JsonUpcase, PasswordOrOtpData},
+ api::{EmptyResult, JsonResult, PasswordOrOtpData},
auth::Headers,
config,
db::{
@@ -34,28 +34,24 @@ fn build_webauthn() -> Result<Webauthn, WebauthnError> {
}
#[post("/two-factor/get-webauthn", data = "<data>")]
-async fn get_webauthn(
- data: JsonUpcase<PasswordOrOtpData>,
- headers: Headers,
- conn: DbConn,
-) -> JsonResult {
- let data: PasswordOrOtpData = data.into_inner().data;
+async fn get_webauthn(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> JsonResult {
+ let data: PasswordOrOtpData = data.into_inner();
let user = headers.user;
data.validate(&user)?;
let keys = WebAuthnInfo::get_all_by_user(&user.uuid, &conn).await?;
Ok(Json(json!({
- "Enabled": !keys.is_empty(),
- "Keys": keys,
- "Object": "twoFactorWebAuthn"
+ "enabled": !keys.is_empty(),
+ "keys": keys,
+ "object": "twoFactorWebAuthn"
})))
}
#[post("/two-factor/get-webauthn-challenge", data = "<data>")]
async fn generate_webauthn_challenge(
- data: JsonUpcase<PasswordOrOtpData>,
+ data: Json<PasswordOrOtpData>,
headers: Headers,
conn: DbConn,
) -> JsonResult {
- let data: PasswordOrOtpData = data.into_inner().data;
+ let data: PasswordOrOtpData = data.into_inner();
let user = headers.user;
data.validate(&user)?;
let (challenge, registration) = build_webauthn()?.start_securitykey_registration(
@@ -93,8 +89,8 @@ async fn activate_webauthn(
let data = data.into_inner();
let user = headers.user;
PasswordOrOtpData {
- MasterPasswordHash: Some(data.master_password_hash),
- Otp: None,
+ master_password_hash: Some(data.master_password_hash),
+ otp: None,
}
.validate(&user)?;
// Retrieve and delete the saved challenge state
@@ -111,9 +107,9 @@ async fn activate_webauthn(
.await?;
let keys = WebAuthnInfo::get_all_by_user(user.uuid.as_str(), &conn).await?;
Ok(Json(json!({
- "Enabled": !keys.is_empty(),
- "Keys": keys,
- "Object": "twoFactorU2f"
+ "enabled": !keys.is_empty(),
+ "keys": keys,
+ "object": "twoFactorWebAuthn"
})))
}
@@ -127,30 +123,26 @@ async fn activate_webauthn_put(
}
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct DeleteU2FData {
- Id: i64,
- MasterPasswordHash: String,
+ id: i64,
+ master_password_hash: String,
}
#[delete("/two-factor/webauthn", data = "<data>")]
-async fn delete_webauthn(
- data: JsonUpcase<DeleteU2FData>,
- headers: Headers,
- conn: DbConn,
-) -> JsonResult {
+async fn delete_webauthn(data: Json<DeleteU2FData>, headers: Headers, conn: DbConn) -> JsonResult {
if !headers
.user
- .check_valid_password(&data.data.MasterPasswordHash)
+ .check_valid_password(&data.master_password_hash)
{
err!("Invalid password");
}
- WebAuthn::delete_by_user_uuid_and_id(&headers.user.uuid, data.data.Id, &conn).await?;
+ WebAuthn::delete_by_user_uuid_and_id(&headers.user.uuid, data.id, &conn).await?;
let keys = WebAuthnInfo::get_all_by_user(&headers.user.uuid, &conn).await?;
Ok(Json(json!({
- "Enabled": !keys.is_empty(),
- "Keys": keys,
- "Object": "twoFactorU2f"
+ "enabled": !keys.is_empty(),
+ "keys": keys,
+ "object": "twoFactorWebAuthn"
})))
}
diff --git a/src/api/core/two_factor/yubikey.rs b/src/api/core/two_factor/yubikey.rs
@@ -1,41 +1,45 @@
-use crate::{
- api::{JsonUpcase, PasswordOrOtpData},
- auth::Headers,
- error::Error,
-};
-use rocket::Route;
+use crate::{api::PasswordOrOtpData, auth::Headers, error::Error};
+use rocket::{serde::json::Json, Route};
pub fn routes() -> Vec<Route> {
routes![activate_yubikey, activate_yubikey_put, generate_yubikey]
}
#[derive(Deserialize)]
-#[allow(non_snake_case, dead_code)]
+#[serde(rename_all = "camelCase")]
struct EnableYubikeyData {
- Key1: Option<String>,
- Key2: Option<String>,
- Key3: Option<String>,
- Key4: Option<String>,
- Key5: Option<String>,
- Nfc: bool,
- MasterPasswordHash: Option<String>,
- Otp: Option<String>,
+ #[allow(dead_code)]
+ key1: Option<String>,
+ #[allow(dead_code)]
+ key2: Option<String>,
+ #[allow(dead_code)]
+ key3: Option<String>,
+ #[allow(dead_code)]
+ key4: Option<String>,
+ #[allow(dead_code)]
+ key5: Option<String>,
+ #[allow(dead_code)]
+ nfc: bool,
+ #[allow(dead_code)]
+ master_password_hash: Option<String>,
+ #[allow(dead_code)]
+ otp: Option<String>,
}
const YUBI_DISABLED_MSG: &str = "Yubico OTP is disabled.";
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/two-factor/get-yubikey", data = "<data>")]
-fn generate_yubikey(data: JsonUpcase<PasswordOrOtpData>, _headers: Headers) -> Error {
+fn generate_yubikey(data: Json<PasswordOrOtpData>, _headers: Headers) -> Error {
Error::new(YUBI_DISABLED_MSG, YUBI_DISABLED_MSG)
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/two-factor/yubikey", data = "<data>")]
-fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, _headers: Headers) -> Error {
+fn activate_yubikey(data: Json<EnableYubikeyData>, _headers: Headers) -> Error {
Error::new(YUBI_DISABLED_MSG, YUBI_DISABLED_MSG)
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[put("/two-factor/yubikey", data = "<data>")]
-fn activate_yubikey_put(data: JsonUpcase<EnableYubikeyData>, _headers: Headers) -> Error {
+fn activate_yubikey_put(data: Json<EnableYubikeyData>, _headers: Headers) -> Error {
Error::new(YUBI_DISABLED_MSG, YUBI_DISABLED_MSG)
}
diff --git a/src/api/identity.rs b/src/api/identity.rs
@@ -1,7 +1,7 @@
use crate::{
api::{
core::accounts::{PreloginData, RegisterData, _prelogin},
- ApiResult, EmptyResult, JsonResult, JsonUpcase,
+ ApiResult, EmptyResult, JsonResult,
},
auth::{ClientHeaders, ClientIp},
config,
@@ -71,15 +71,22 @@ async fn _refresh_login(data: ConnectData, conn: &DbConn) -> JsonResult {
"expires_in": expires_in,
"token_type": "Bearer",
"refresh_token": device.refresh_token,
- "Key": user.akey,
+ "scope": scope,
"PrivateKey": user.private_key,
+ "Key": user.akey,
+ "MasterPasswordPolicy": {
+ "Object": "masterPasswordPolicy"
+ },
+ "ForcePasswordReset": false,
+ "ResetMasterPassword": false,
"Kdf": user.client_kdf_type,
"KdfIterations": user.client_kdf_iter(),
"KdfMemory": user.client_kdf_memory(),
"KdfParallelism": user.client_kdf_parallelism(),
- "ResetMasterPassword": false, // TODO: according to official server seems something like: user.password_hash.is_empty(), but would need testing
- "scope": scope,
- "unofficialServer": true,
+ "UserDecryptionOptions": {
+ "HasMasterPassword": true,
+ "Object": "userDecryptionOptions"
+ },
});
Ok(Json(result))
}
@@ -160,7 +167,11 @@ async fn _password_login(
"KdfIterations": kdf_iter,
"KdfMemory": kdf_mem,
"KdfParallelism": kdf_par,
- "ResetMasterPassword": false,// TODO: Same as above
+ "ResetMasterPassword": false, // TODO: Same as above
+ "ForcePasswordReset": false,
+ "MasterPasswordPolicy": {
+ "object": "masterPasswordPolicy",
+ },
"scope": scope,
"unofficialServer": true,
"UserDecryptionOptions": {
@@ -280,7 +291,10 @@ async fn _json_err_twofactor(
"error" : "invalid_grant",
"error_description" : "Two factor required.",
"TwoFactorProviders" : if authn { if totp { providers.as_slice() } else { &providers[..1] } } else if totp { &providers[1..] } else { [].as_slice() },
- "TwoFactorProviders2" : {} // { "0" : null }
+ "TwoFactorProviders2" : {},
+ "MasterPasswordPolicy": {
+ "Object": "masterPasswordPolicy"
+ }
});
if authn {
let request = two_factor::webauthn::generate_webauthn_login(user_uuid, conn).await?;
@@ -293,13 +307,13 @@ async fn _json_err_twofactor(
}
#[post("/accounts/prelogin", data = "<data>")]
-async fn prelogin(data: JsonUpcase<PreloginData>, conn: DbConn) -> Json<Value> {
+async fn prelogin(data: Json<PreloginData>, conn: DbConn) -> Json<Value> {
_prelogin(data, conn).await
}
#[allow(unused_variables, clippy::needless_pass_by_value)]
#[post("/accounts/register", data = "<data>")]
-fn identity_register(data: JsonUpcase<RegisterData>) -> Error {
+fn identity_register(data: Json<RegisterData>) -> Error {
const MSG: &str = "No more registerations allowed.";
Error::new(MSG, MSG)
}
@@ -307,7 +321,6 @@ fn identity_register(data: JsonUpcase<RegisterData>) -> Error {
// https://github.com/bitwarden/jslib/blob/master/common/src/models/request/tokenRequest.ts
// https://github.com/bitwarden/mobile/blob/master/src/Core/Models/Request/TokenRequest.cs
#[derive(Clone, Default, FromForm)]
-#[allow(non_snake_case)]
struct ConnectData {
#[field(name = uncased("grant_type"))]
#[field(name = uncased("granttype"))]
diff --git a/src/api/mod.rs b/src/api/mod.rs
@@ -11,23 +11,19 @@ pub use crate::api::{
};
use crate::db::models::User;
use crate::error::Error;
-use crate::util;
use rocket::serde::json::Json;
use serde_json::Value;
// Type aliases for API methods results
type ApiResult<T> = Result<T, Error>;
type JsonResult = ApiResult<Json<Value>>;
pub type EmptyResult = ApiResult<()>;
-type JsonUpcase<T> = Json<util::UpCase<T>>;
-type JsonUpcaseVec<T> = Json<Vec<util::UpCase<T>>>;
-type JsonVec<T> = Json<Vec<T>>;
// Common structs representing JSON data received
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct PasswordOrOtpData {
- MasterPasswordHash: Option<String>,
- Otp: Option<String>,
+ master_password_hash: Option<String>,
+ otp: Option<String>,
}
impl PasswordOrOtpData {
@@ -35,7 +31,7 @@ impl PasswordOrOtpData {
/// First for the validation to continue, after that to enable or validate the following actions
/// This is different per caller, so it can be adjusted to delete the token or not
fn validate(&self, user: &User) -> EmptyResult {
- match (self.MasterPasswordHash.as_deref(), self.Otp.as_deref()) {
+ match (self.master_password_hash.as_deref(), self.otp.as_deref()) {
(Some(pw_hash), None) => {
if !user.check_valid_password(pw_hash) {
err!("Invalid password");
@@ -49,29 +45,3 @@ impl PasswordOrOtpData {
Ok(())
}
}
-
-#[derive(Deserialize, Clone)]
-#[serde(untagged)]
-enum NumberOrString {
- Number(i32),
- String(String),
-}
-
-impl NumberOrString {
- fn into_string(self) -> String {
- match self {
- Self::Number(n) => n.to_string(),
- Self::String(s) => s,
- }
- }
- #[allow(clippy::wrong_self_convention)]
- fn into_i32(&self) -> ApiResult<i32> {
- use std::num::ParseIntError as PIE;
- match *self {
- Self::Number(n) => Ok(n),
- Self::String(ref s) => s
- .parse()
- .map_err(|e: PIE| Error::new("Can't convert to number", e.to_string())),
- }
- }
-}
diff --git a/src/api/web.rs b/src/api/web.rs
@@ -135,9 +135,9 @@ fn static_files(filename: &str) -> Result<(ContentType, &'static [u8]), Error> {
ContentType::JavaScript,
include_bytes!("../static/scripts/bootstrap.bundle.js"),
)),
- "jdenticon.js" => Ok((
+ "jdenticon-3.3.0.js" => Ok((
ContentType::JavaScript,
- include_bytes!("../static/scripts/jdenticon.js"),
+ include_bytes!("../static/scripts/jdenticon-3.3.0.js"),
)),
"datatables.js" => Ok((
ContentType::JavaScript,
@@ -147,9 +147,9 @@ fn static_files(filename: &str) -> Result<(ContentType, &'static [u8]), Error> {
ContentType::CSS,
include_bytes!("../static/scripts/datatables.css"),
)),
- "jquery-3.7.0.slim.js" => Ok((
+ "jquery-3.7.1.slim.js" => Ok((
ContentType::JavaScript,
- include_bytes!("../static/scripts/jquery-3.7.0.slim.js"),
+ include_bytes!("../static/scripts/jquery-3.7.1.slim.js"),
)),
_ => err!(format!("Static file not found: {filename}")),
}
diff --git a/src/auth.rs b/src/auth.rs
@@ -2,7 +2,7 @@ use crate::{
config::{self, Config},
error::Error,
};
-use chrono::{Duration, Utc};
+use chrono::{TimeDelta, Utc};
use jsonwebtoken::{self, errors::ErrorKind, Algorithm, DecodingKey, EncodingKey, Header};
use openssl::pkey::{Id, PKey};
use serde::de::DeserializeOwned;
@@ -11,15 +11,15 @@ use std::fs::File;
use std::io::{Read, Write};
use std::sync::OnceLock;
-static DEFAULT_VALIDITY: OnceLock<Duration> = OnceLock::new();
+static DEFAULT_VALIDITY: OnceLock<TimeDelta> = OnceLock::new();
#[inline]
fn init_default_validity() {
DEFAULT_VALIDITY
- .set(Duration::hours(2))
+ .set(TimeDelta::try_hours(2).expect("TimeDelta::try_hours(2) should work"))
.expect("DEFAULT_VALIDITY must only be initialized once");
}
#[inline]
-pub fn get_default_validity() -> &'static Duration {
+pub fn get_default_validity() -> &'static TimeDelta {
DEFAULT_VALIDITY
.get()
.expect("DEFAULT_VALIDITY must be initialized in main")
@@ -291,8 +291,6 @@ impl<'r> FromRequest<'r> for Host {
}
pub struct ClientHeaders {
#[allow(dead_code)]
- pub host: String,
- #[allow(dead_code)]
pub device_type: i32,
pub ip: ClientIp,
}
@@ -301,7 +299,6 @@ pub struct ClientHeaders {
impl<'r> FromRequest<'r> for ClientHeaders {
type Error = &'static str;
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
- let host = try_outcome!(Host::from_request(request).await).host;
let Outcome::Success(ip) = ClientIp::from_request(request).await else {
err_handler!("Error getting Client IP")
};
@@ -311,11 +308,7 @@ impl<'r> FromRequest<'r> for ClientHeaders {
.get_one("device-type")
.map_or(14i32, |d| d.parse().unwrap_or(14i32));
- Outcome::Success(Self {
- host,
- device_type,
- ip,
- })
+ Outcome::Success(Self { device_type, ip })
}
}
@@ -408,8 +401,6 @@ pub struct OrgHeaders {
user: User,
org_user_type: UserOrgType,
org_user: UserOrganization,
- #[allow(dead_code)]
- pub org_id: String,
ip: ClientIp,
}
@@ -472,7 +463,6 @@ impl<'r> FromRequest<'r> for OrgHeaders {
}
},
org_user,
- org_id: String::from(org_id),
ip: headers.ip,
})
}
@@ -549,8 +539,6 @@ pub struct ManagerHeaders {
host: String,
device: Device,
pub user: User,
- #[allow(dead_code)]
- pub org_user_type: UserOrgType,
ip: ClientIp,
}
@@ -566,7 +554,7 @@ impl<'r> FromRequest<'r> for ManagerHeaders {
err_handler!("Error getting DB")
};
- if !can_access_collection(&headers.org_user, &col_id, &conn).await {
+ if !Collection::can_access_collection(&headers.org_user, &col_id, &conn).await {
err_handler!("The current user isn't a manager for this collection")
}
}
@@ -577,7 +565,6 @@ impl<'r> FromRequest<'r> for ManagerHeaders {
host: headers.host,
device: headers.device,
user: headers.user,
- org_user_type: headers.org_user_type,
ip: headers.ip,
})
} else {
@@ -604,7 +591,6 @@ pub struct ManagerHeadersLoose {
device: Device,
pub user: User,
pub org_user: UserOrganization,
- org_user_type: UserOrgType,
ip: ClientIp,
}
@@ -619,7 +605,6 @@ impl<'r> FromRequest<'r> for ManagerHeadersLoose {
device: headers.device,
user: headers.user,
org_user: headers.org_user,
- org_user_type: headers.org_user_type,
ip: headers.ip,
})
} else {
@@ -638,11 +623,6 @@ impl From<ManagerHeadersLoose> for Headers {
}
}
}
-async fn can_access_collection(org_user: &UserOrganization, col_id: &str, conn: &DbConn) -> bool {
- org_user.has_full_access()
- || Collection::has_access_by_collection_and_user_uuid(col_id, &org_user.user_uuid, conn)
- .await
-}
impl ManagerHeaders {
pub async fn from_loose(
@@ -654,7 +634,7 @@ impl ManagerHeaders {
if uuid::Uuid::parse_str(col_id).is_err() {
err!("Collection Id is malformed!");
}
- if !can_access_collection(&h.org_user, col_id, conn).await {
+ if !Collection::can_access_collection(&h.org_user, col_id, conn).await {
err!("You don't have access to all collections!");
}
}
@@ -662,7 +642,6 @@ impl ManagerHeaders {
host: h.host,
device: h.device,
user: h.user,
- org_user_type: h.org_user_type,
ip: h.ip,
})
}
@@ -670,8 +649,6 @@ impl ManagerHeaders {
pub struct OwnerHeaders {
#[allow(dead_code)]
- pub host: String,
- #[allow(dead_code)]
pub device: Device,
pub user: User,
#[allow(dead_code)]
@@ -685,7 +662,6 @@ impl<'r> FromRequest<'r> for OwnerHeaders {
let headers = try_outcome!(OrgHeaders::from_request(request).await);
if headers.org_user_type == UserOrgType::Owner {
Outcome::Success(Self {
- host: headers.host,
device: headers.device,
user: headers.user,
ip: headers.ip,
diff --git a/src/db/models/cipher.rs b/src/db/models/cipher.rs
@@ -66,7 +66,7 @@ impl Cipher {
pub fn validate_notes(cipher_data: &[CipherData]) -> EmptyResult {
let mut validation_errors = serde_json::Map::new();
for (index, cipher) in cipher_data.iter().enumerate() {
- if let Some(ref note) = cipher.Notes {
+ if let Some(ref note) = cipher.notes {
if note.len() > 10_000 {
validation_errors.insert(
format!("Ciphers[{index}].Notes"),
@@ -139,21 +139,28 @@ impl Cipher {
type_data_json["Uri"] = Value::Null;
}
}
+ if self.atype == 2i32
+ && (self.data.is_empty()
+ || self.data.eq("{}")
+ || self.data.to_ascii_lowercase().eq("{\"type\":null}"))
+ {
+ type_data_json = json!({"type": 0i32});
+ }
// Clone the type_data and add some default value.
let mut data_json = type_data_json.clone();
// NOTE: This was marked as *Backwards Compatibility Code*, but as of January 2021 this is still being used by upstream
// data_json should always contain the following keys with every atype
- data_json["Fields"] = fields_json.clone();
- data_json["Name"] = json!(self.name);
- data_json["Notes"] = json!(self.notes);
- data_json["PasswordHistory"] = password_history_json.clone();
+ data_json["fields"] = fields_json.clone();
+ data_json["name"] = json!(self.name);
+ data_json["notes"] = json!(self.notes);
+ data_json["passwordHistory"] = password_history_json.clone();
let collection_ids = if let Some(cipher_sync_data) = cipher_sync_data {
cipher_sync_data
.cipher_collections
.get(&self.uuid)
.map_or_else(|| Cow::from(Vec::with_capacity(0)), Cow::from)
} else {
- Cow::from(self.get_collections(user_uuid.to_owned(), conn).await)
+ Cow::from(self.get_admin_collections(user_uuid.to_owned(), conn).await)
};
// There are three types of cipher response models in upstream
// Bitwarden: "cipherMini", "cipher", and "cipherDetails" (in order
@@ -163,37 +170,37 @@ impl Cipher {
//
// Ref: https://github.com/bitwarden/server/blob/master/src/Core/Models/Api/Response/CipherResponseModel.cs
let mut json_object = json!({
- "Object": "cipherDetails",
- "Id": self.uuid,
- "Type": self.atype,
- "CreationDate": util::format_date(&self.created_at),
- "RevisionDate": util::format_date(&self.updated_at),
- "DeletedDate": self.deleted_at.map_or(Value::Null, |d| Value::String(util::format_date(&d))),
- "Reprompt": self.reprompt.unwrap_or_else(|| i32::from(RepromptType::None)),
- "OrganizationId": self.organization_uuid,
- "Key": self.key,
- "Attachments": Value::Null,
+ "object": "cipherDetails",
+ "id": self.uuid,
+ "type": self.atype,
+ "creationDate": util::format_date(&self.created_at),
+ "revisionDate": util::format_date(&self.updated_at),
+ "deletedDate": self.deleted_at.map_or(Value::Null, |d| Value::String(util::format_date(&d))),
+ "reprompt": self.reprompt.unwrap_or_else(|| i32::from(RepromptType::None)),
+ "organizationId": self.organization_uuid,
+ "key": self.key,
+ "attachments": Value::Null,
// We have UseTotp set to true by default within the Organization model.
// This variable together with UsersGetPremium is used to show or hide the TOTP counter.
- "OrganizationUseTotp": true,
+ "organizationUseTotp": true,
// This field is specific to the cipherDetails type.
- "CollectionIds": collection_ids,
- "Name": self.name,
- "Notes": self.notes,
- "Fields": fields_json,
- "Data": data_json,
- "PasswordHistory": password_history_json,
+ "collectionIds": collection_ids,
+ "name": self.name,
+ "notes": self.notes,
+ "fields": fields_json,
+ "data": data_json,
+ "passwordHistory": password_history_json,
// All Cipher types are included by default as null, but only the matching one will be populated
- "Login": null,
- "SecureNote": null,
- "Card": null,
- "Identity": null,
+ "login": null,
+ "secureNote": null,
+ "card": null,
+ "identity": null,
});
// These values are only needed for user/default syncs
// Not during an organizational sync like `get_org_details`
// Skip adding these fields in that case
if sync_type == CipherSyncType::User {
- json_object["FolderId"] = json!(if let Some(cipher_sync_data) = cipher_sync_data {
+ json_object["folderId"] = json!(if let Some(cipher_sync_data) = cipher_sync_data {
cipher_sync_data
.cipher_folders
.get(&self.uuid)
@@ -201,7 +208,7 @@ impl Cipher {
} else {
self.get_folder_uuid(user_uuid, conn).await
});
- json_object["Favorite"] = json!(if let Some(cipher_sync_data) = cipher_sync_data {
+ json_object["favorite"] = json!(if let Some(cipher_sync_data) = cipher_sync_data {
cipher_sync_data.cipher_favorites.contains(&self.uuid)
} else {
self.is_favorite(user_uuid, conn).await
@@ -209,15 +216,15 @@ impl Cipher {
// These values are true by default, but can be false if the
// cipher belongs to a collection or group where the org owner has enabled
// the "Read Only" or "Hide Passwords" restrictions for the user.
- json_object["Edit"] = json!(!read_only);
- json_object["ViewPassword"] = json!(!hide_passwords);
+ json_object["edit"] = json!(!read_only);
+ json_object["viewPassword"] = json!(!hide_passwords);
}
let key = match self.atype {
- 1i32 => "Login",
- 2i32 => "SecureNote",
- 3i32 => "Card",
- 4i32 => "Identity",
+ 1i32 => "login",
+ 2i32 => "secureNote",
+ 3i32 => "card",
+ 4i32 => "identity",
_ => panic!("Wrong type"),
};
@@ -490,6 +497,21 @@ impl Cipher {
}}
}
+ pub async fn find_by_uuid_and_org(
+ cipher_uuid: &str,
+ org_uuid: &str,
+ conn: &DbConn,
+ ) -> Option<Self> {
+ db_run! {conn: {
+ ciphers::table
+ .filter(ciphers::uuid.eq(cipher_uuid))
+ .filter(ciphers::organization_uuid.eq(org_uuid))
+ .first::<CipherDb>(conn)
+ .ok()
+ .from_db()
+ }}
+ }
+
// Find all ciphers accessible or visible to the specified user.
//
// "Accessible" means the user has read access to the cipher, either via
@@ -606,27 +628,49 @@ impl Cipher {
pub async fn get_collections(&self, user_id: String, conn: &DbConn) -> Vec<String> {
db_run! {conn: {
ciphers_collections::table
- .inner_join(collections::table.on(
- collections::uuid.eq(ciphers_collections::collection_uuid)
- ))
- .inner_join(users_organizations::table.on(
- users_organizations::org_uuid.eq(collections::org_uuid).and(
- users_organizations::user_uuid.eq(user_id.clone())
- )
- ))
- .left_join(users_collections::table.on(
- users_collections::collection_uuid.eq(ciphers_collections::collection_uuid).and(
- users_collections::user_uuid.eq(user_id.clone())
+ .filter(ciphers_collections::cipher_uuid.eq(&self.uuid))
+ .inner_join(collections::table.on(
+ collections::uuid.eq(ciphers_collections::collection_uuid)
+ ))
+ .inner_join(users_organizations::table.on(
+ users_organizations::org_uuid.eq(collections::org_uuid)
+ .and(users_organizations::user_uuid.eq(user_id.clone()))
+ ))
+ .left_join(users_collections::table.on(
+ users_collections::collection_uuid.eq(ciphers_collections::collection_uuid)
+ .and(users_collections::user_uuid.eq(user_id.clone()))
+ ))
+ .filter(users_organizations::access_all.eq(true) // User has access all
+ .or(users_collections::user_uuid.eq(user_id) // User has access to collection
+ .and(users_collections::read_only.eq(false)))
)
- ))
- .filter(ciphers_collections::cipher_uuid.eq(&self.uuid))
- .filter(users_collections::user_uuid.eq(user_id).or( // User has access to collection
- users_organizations::access_all.eq(true).or( // User has access all
- users_organizations::atype.le(i32::from(UserOrgType::Admin)) // User is admin or owner
+ .select(ciphers_collections::collection_uuid)
+ .load::<String>(conn).unwrap_or_default()
+ }}
+ }
+
+ pub async fn get_admin_collections(&self, user_id: String, conn: &DbConn) -> Vec<String> {
+ db_run! {conn: {
+ ciphers_collections::table
+ .filter(ciphers_collections::cipher_uuid.eq(&self.uuid))
+ .inner_join(collections::table.on(
+ collections::uuid.eq(ciphers_collections::collection_uuid)
+ ))
+ .inner_join(users_organizations::table.on(
+ users_organizations::org_uuid.eq(collections::org_uuid)
+ .and(users_organizations::user_uuid.eq(user_id.clone()))
+ ))
+ .left_join(users_collections::table.on(
+ users_collections::collection_uuid.eq(ciphers_collections::collection_uuid)
+ .and(users_collections::user_uuid.eq(user_id.clone()))
+ ))
+ .filter(users_organizations::access_all.eq(true) // User has access all
+ .or(users_collections::user_uuid.eq(user_id) // User has access to collection
+ .and(users_collections::read_only.eq(false)))
+ .or(users_organizations::atype.le(i32::from(UserOrgType::Admin))) // User is admin or owner
)
- ))
- .select(ciphers_collections::collection_uuid)
- .load::<String>(conn).unwrap_or_default()
+ .select(ciphers_collections::collection_uuid)
+ .load::<String>(conn).unwrap_or_default()
}}
}
diff --git a/src/db/models/collection.rs b/src/db/models/collection.rs
@@ -46,11 +46,11 @@ impl Collection {
pub fn to_json(&self) -> Value {
json!({
- "ExternalId": self.external_id,
- "Id": self.uuid,
- "OrganizationId": self.org_uuid,
- "Name": self.name,
- "Object": "collection",
+ "externalId": self.external_id,
+ "id": self.uuid,
+ "organizationId": self.org_uuid,
+ "name": self.name,
+ "object": "collection",
})
}
@@ -91,11 +91,26 @@ impl Collection {
)
};
let mut json_object = self.to_json();
- json_object["Object"] = json!("collectionDetails");
- json_object["ReadOnly"] = json!(read_only);
- json_object["HidePasswords"] = json!(hide_passwords);
+ json_object["object"] = json!("collectionDetails");
+ json_object["readOnly"] = json!(read_only);
+ json_object["hidePasswords"] = json!(hide_passwords);
json_object
}
+
+ pub async fn can_access_collection(
+ org_user: &UserOrganization,
+ col_id: &str,
+ conn: &DbConn,
+ ) -> bool {
+ org_user.has_status(UserOrgStatus::Confirmed)
+ && (org_user.has_full_access()
+ || CollectionUser::has_access_to_collection_by_user(
+ col_id,
+ &org_user.user_uuid,
+ conn,
+ )
+ .await)
+ }
}
use crate::api::EmptyResult;
@@ -191,20 +206,6 @@ impl Collection {
}}
}
- // Check if a user has access to a specific collection
- // FIXME: This needs to be reviewed. The query used by `find_by_user_uuid` could be adjusted to filter when needed.
- // For now this is a good solution without making too much changes.
- pub async fn has_access_by_collection_and_user_uuid(
- collection_uuid: &str,
- user_uuid: &str,
- conn: &DbConn,
- ) -> bool {
- Self::find_by_user_uuid(user_uuid.to_owned(), conn)
- .await
- .into_iter()
- .any(|c| c.uuid == collection_uuid)
- }
-
pub async fn find_by_organization_and_user_uuid(
org_uuid: &str,
user_uuid: &str,
@@ -285,27 +286,24 @@ impl Collection {
let user_uuid = user_uuid.to_owned();
db_run! { conn: {
collections::table
- .left_join(users_collections::table.on(
- users_collections::collection_uuid.eq(collections::uuid).and(
- users_collections::user_uuid.eq(user_uuid.clone())
- )
- ))
- .left_join(users_organizations::table.on(
- collections::org_uuid.eq(users_organizations::org_uuid).and(
- users_organizations::user_uuid.eq(user_uuid)
- )
- ))
- .filter(collections::uuid.eq(&self.uuid))
- .filter(
- users_collections::collection_uuid.eq(&self.uuid).and(users_collections::read_only.eq(false)).or(// Directly accessed collection
- users_organizations::access_all.eq(true).or( // access_all in Organization
- users_organizations::atype.le(i32::from(UserOrgType::Admin)) // Org admin or owner
+ .filter(collections::uuid.eq(&self.uuid))
+ .inner_join(users_organizations::table.on(
+ collections::org_uuid.eq(users_organizations::org_uuid)
+ .and(users_organizations::user_uuid.eq(user_uuid.clone()))
))
- )
- .count()
- .first::<i64>(conn)
- .ok()
- .unwrap_or(0) != 0
+ .left_join(users_collections::table.on(
+ users_collections::collection_uuid.eq(collections::uuid)
+ .and(users_collections::user_uuid.eq(user_uuid))
+ ))
+ .filter(users_organizations::atype.le(i32::from(UserOrgType::Admin)) // Org admin or owner
+ .or(users_organizations::access_all.eq(true)) // access_all via membership
+ .or(users_collections::collection_uuid.eq(&self.uuid) // write access given to collection
+ .and(users_collections::read_only.eq(false)))
+ )
+ .count()
+ .first::<i64>(conn)
+ .ok()
+ .unwrap_or(0) != 0
}}
}
@@ -506,6 +504,16 @@ impl CollectionUser {
Ok(())
}}
}
+
+ pub async fn has_access_to_collection_by_user(
+ col_id: &str,
+ user_uuid: &str,
+ conn: &DbConn,
+ ) -> bool {
+ Self::find_by_collection_and_user(col_id, user_uuid, conn)
+ .await
+ .is_some()
+ }
}
/// Database methods
diff --git a/src/db/models/device.rs b/src/db/models/device.rs
@@ -49,8 +49,8 @@ impl Device {
self.refresh_token = crypto::encode_random_bytes::<64>(&BASE64URL);
}
// Update the expiration of the device and the last update date
- let time_now = Utc::now().naive_utc();
- self.updated_at = time_now;
+ let time_now = Utc::now();
+ self.updated_at = time_now.naive_utc();
// ---
// Disabled these keys to be added to the JWT since they could cause the JWT to get too large
// Also These key/value pairs are not used anywhere by either Vaultwarden or Bitwarden Clients
@@ -65,10 +65,10 @@ impl Device {
// Create the JWT claims struct, to send to the client
use crate::auth::{self, encode_jwt, LoginJwtClaims};
let claims = LoginJwtClaims {
- nbf: time_now.and_utc().timestamp(),
- exp: (time_now.checked_add_signed(*auth::get_default_validity()))
- .expect("Duration add overflowed")
- .and_utc()
+ nbf: time_now.timestamp(),
+ exp: time_now
+ .checked_add_signed(*auth::get_default_validity())
+ .expect("overflow")
.timestamp(),
iss: auth::get_jwt_login_issuer().to_owned(),
sub: user.uuid.clone(),
diff --git a/src/db/models/org_policy.rs b/src/db/models/org_policy.rs
@@ -3,7 +3,7 @@ use crate::api::EmptyResult;
use crate::db::models::TwoFactorType;
use crate::db::DbConn;
use crate::error::{Error, MapResult};
-use crate::util::{self, UpCase};
+use crate::util;
use diesel::result::{self, DatabaseErrorKind};
use serde::Deserialize;
use serde_json::Value;
@@ -68,9 +68,9 @@ impl TryFrom<i32> for OrgPolicyType {
// https://github.com/bitwarden/server/blob/5cbdee137921a19b1f722920f0fa3cd45af2ef0f/src/Core/Models/Data/Organizations/Policies/ResetPasswordDataModel.cs
#[derive(Deserialize)]
-#[allow(non_snake_case)]
+#[serde(rename_all = "camelCase")]
struct ResetPasswordDataModel {
- AutoEnrollEnabled: bool,
+ auto_enroll_enabled: bool,
}
type OrgPolicyResult = Result<(), OrgPolicyErr>;
pub enum OrgPolicyErr {
@@ -93,12 +93,12 @@ impl OrgPolicy {
pub fn to_json(&self) -> Value {
let data_json: Value = serde_json::from_str(&self.data).unwrap_or(Value::Null);
json!({
- "Id": self.uuid,
- "OrganizationId": self.org_uuid,
- "Type": self.atype,
- "Data": data_json,
- "Enabled": self.enabled,
- "Object": "policy",
+ "id": self.uuid,
+ "organizationId": self.org_uuid,
+ "type": self.atype,
+ "data": data_json,
+ "enabled": self.enabled,
+ "object": "policy",
})
}
}
@@ -273,10 +273,8 @@ impl OrgPolicy {
pub async fn org_is_reset_password_auto_enroll(org_uuid: &str, conn: &DbConn) -> bool {
match Self::find_by_org_and_type(org_uuid, OrgPolicyType::ResetPassword, conn).await {
Some(policy) => {
- if let Ok(opts) =
- serde_json::from_str::<UpCase<ResetPasswordDataModel>>(&policy.data)
- {
- return policy.enabled && opts.data.AutoEnrollEnabled;
+ if let Ok(opts) = serde_json::from_str::<ResetPasswordDataModel>(&policy.data) {
+ return policy.enabled && opts.auto_enroll_enabled;
}
}
None => return false,
diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs
@@ -191,34 +191,35 @@ impl Organization {
// https://github.com/bitwarden/server/blob/13d1e74d6960cf0d042620b72d85bf583a4236f7/src/Api/Models/Response/Organizations/OrganizationResponseModel.cs
pub fn to_json(&self) -> Value {
json!({
- "Id": self.uuid,
- "Identifier": null, // not supported by us
- "Name": self.name,
- "Seats": 10u32, // The value doesn't matter, we don't check server-side
- "MaxCollections": 10u32, // The value doesn't matter, we don't check server-side
- "MaxStorageGb": 10u32, // The value doesn't matter, we don't check server-side
- "Use2fa": true,
- "UseDirectory": false, // Is supported, but this value isn't checked anywhere (yet)
- "UseEvents": false,
- "UseGroups": false,
- "UseTotp": true,
- "UsePolicies": true,
- "UseSso": false, // Not supported
- "SelfHost": true,
- "UseApi": true,
- "HasPublicAndPrivateKeys": self.private_key.is_some() && self.public_key.is_some(),
- "UseResetPassword": false,
- "BusinessName": null,
- "BusinessAddress1": null,
- "BusinessAddress2": null,
- "BusinessAddress3": null,
- "BusinessCountry": null,
- "BusinessTaxNumber": null,
- "BillingEmail": self.billing_email,
- "Plan": "TeamsAnnually",
- "PlanType": 5u32, // TeamsAnnually plan
- "UsersGetPremium": true,
- "Object": "organization",
+ "id": self.uuid,
+ "identifier": null, // not supported by us
+ "name": self.name,
+ "seats": null,
+ "maxAutoscaleSeats": null,
+ "maxCollections": null,
+ "maxStorageGb": i16::MAX,
+ "use2fa": true,
+ "useCustomPermissions": false,
+ "useDirectory": false, // Is supported, but this value isn't checked anywhere (yet)
+ "useEvents": false,
+ "useGroups": false,
+ "useTotp": true,
+ "usePolicies": true,
+ "useSso": false, // Not supported
+ "selfHost": true,
+ "useApi": true,
+ "hasPublicAndPrivateKeys": self.private_key.is_some() && self.public_key.is_some(),
+ "useResetPassword": false,
+ "businessName": null,
+ "businessAddress1": null,
+ "businessAddress2": null,
+ "businessAddress3": null,
+ "businessCountry": null,
+ "businessTaxNumber": null,
+ "billingEmail": self.billing_email,
+ "planType": 6,
+ "usersGetPremium": true,
+ "object": "organization",
})
}
}
@@ -340,36 +341,77 @@ impl UserOrganization {
let org = Organization::find_by_uuid(&self.org_uuid, conn)
.await
.unwrap();
+ let permissions = json!({
+ // TODO: Add support for Custom User Roles
+ // See: https://bitwarden.com/help/article/user-types-access-control/#custom-role
+ "accessEventLogs": false,
+ "accessImportExport": false,
+ "accessReports": false,
+ "createNewCollections": false,
+ "editAnyCollection": false,
+ "deleteAnyCollection": false,
+ "editAssignedCollections": false,
+ "deleteAssignedCollections": false,
+ "manageGroups": false,
+ "managePolicies": false,
+ "manageSso": false, // Not supported
+ "manageUsers": false,
+ "manageResetPassword": false,
+ "manageScim": false // Not supported (Not AGPLv3 Licensed)
+ });
// https://github.com/bitwarden/server/blob/13d1e74d6960cf0d042620b72d85bf583a4236f7/src/Api/Models/Response/ProfileOrganizationResponseModel.cs
json!({
- "Id": self.org_uuid,
- "Identifier": null, // Not supported
- "Name": org.name,
- "Seats": 10u32, // The value doesn't matter, we don't check server-side
- "MaxCollections": 10u32, // The value doesn't matter, we don't check server-side
- "UsersGetPremium": true,
- "Use2fa": true,
- "UseDirectory": false, // Is supported, but this value isn't checked anywhere (yet)
- "UseEvents": false,
- "UseGroups": false,
- "UseTotp": true,
- "UsePolicies": true,
- "UseApi": true,
- "SelfHost": true,
- "HasPublicAndPrivateKeys": org.private_key.is_some() && org.public_key.is_some(),
- "ResetPasswordEnrolled": self.reset_password_key.is_some(),
- "UseResetPassword": false,
- "SsoBound": false, // Not supported
- "UseSso": false, // Not supported
- "ProviderId": null,
- "ProviderName": null,
- "MaxStorageGb": 10u32, // The value doesn't matter, we don't check server-side
- "UserId": self.user_uuid,
- "Key": self.akey,
- "Status": self.status,
- "Type": self.atype,
- "Enabled": true,
- "Object": "profileOrganization",
+ "id": self.org_uuid,
+ "identifier": null, // Not supported
+ "name": org.name,
+ "seats": null,
+ "maxAutoscaleSeats": null,
+ "maxCollections": null,
+ "usersGetPremium": true,
+ "use2fa": true,
+ "useDirectory": false, // Is supported, but this value isn't checked anywhere (yet)
+ "useEvents": false,
+ "useGroups": false,
+ "useTotp": true,
+ "useScim": false, // Not supported (Not AGPLv3 Licensed)
+ "usePolicies": true,
+ "useApi": false,
+ "selfHost": true,
+ "hasPublicAndPrivateKeys": org.private_key.is_some() && org.public_key.is_some(),
+ "resetPasswordEnrolled": self.reset_password_key.is_some(),
+ "useResetPassword": false,
+ "ssoBound": false, // Not supported
+ "useSso": false, // Not supported
+ "useKeyConnector": false,
+ "useSecretsManager": false,
+ "usePasswordManager": true,
+ "useCustomPermissions": false,
+ "useActivateAutofillPolicy": false,
+ "organizationUserId": self.uuid,
+ "providerId": null,
+ "providerName": null,
+ "providerType": null,
+ "familySponsorshipFriendlyName": null,
+ "familySponsorshipAvailable": false,
+ "planProductType": 3i32,
+ "productTierType": 3i32,
+ "keyConnectorEnabled": false,
+ "keyConnectorUrl": null,
+ "familySponsorshipLastSyncDate": null,
+ "familySponsorshipValidUntil": null,
+ "familySponsorshipToDelete": null,
+ "accessSecretsManager": false,
+ "limitCollectionCreationDeletion": true,
+ "allowAdminAccessToAllCollectionItems": true,
+ "flexibleCollections": false,
+ "permissions": permissions,
+ "maxStorageGb": i16::MAX,
+ "userId": self.user_uuid,
+ "key": self.akey,
+ "status": self.status,
+ "type": self.atype,
+ "enabled": true,
+ "object": "profileOrganization",
})
}
pub async fn to_json_user_details(&self, include_collections: bool, conn: &DbConn) -> Value {
@@ -396,9 +438,9 @@ impl UserOrganization {
.iter()
.map(|cu| {
json!({
- "Id": cu.collection_uuid,
- "ReadOnly": cu.read_only,
- "HidePasswords": cu.hide_passwords,
+ "id": cu.collection_uuid,
+ "readOnly": cu.read_only,
+ "hidePasswords": cu.hide_passwords,
})
})
.collect()
@@ -407,27 +449,27 @@ impl UserOrganization {
};
json!({
- "Id": self.uuid,
- "UserId": self.user_uuid,
- "Name": user.name,
- "Email": user.email,
- "ExternalId": self.external_id,
- "Groups": groups,
- "Collections": collections,
- "Status": status,
- "Type": self.atype,
- "AccessAll": self.access_all,
- "TwoFactorEnabled": twofactor_enabled,
- "ResetPasswordEnrolled": self.reset_password_key.is_some(),
- "Object": "organizationUserUserDetails",
+ "id": self.uuid,
+ "userId": self.user_uuid,
+ "name": user.name,
+ "email": user.email,
+ "externalId": self.external_id,
+ "groups": groups,
+ "collections": collections,
+ "status": status,
+ "type": self.atype,
+ "accessAll": self.access_all,
+ "twoFactorEnabled": twofactor_enabled,
+ "resetPasswordEnrolled": self.reset_password_key.is_some(),
+ "object": "organizationUserUserDetails",
})
}
pub fn to_json_user_access_restrictions(&self, col_user: &CollectionUser) -> Value {
json!({
- "Id": self.uuid,
- "ReadOnly": col_user.read_only,
- "HidePasswords": col_user.hide_passwords,
+ "id": self.uuid,
+ "readOnly": col_user.read_only,
+ "hidePasswords": col_user.hide_passwords,
})
}
@@ -488,7 +530,7 @@ impl UserOrganization {
None
}
- fn has_status(&self, status: UserOrgStatus) -> bool {
+ pub fn has_status(&self, status: UserOrgStatus) -> bool {
self.status == i32::from(status)
}
@@ -616,6 +658,23 @@ impl UserOrganization {
}}
}
+ pub async fn find_confirmed_by_user_and_org(
+ user_uuid: &str,
+ org_uuid: &str,
+ conn: &DbConn,
+ ) -> Option<Self> {
+ db_run! { conn: {
+ users_organizations::table
+ .filter(users_organizations::user_uuid.eq(user_uuid))
+ .filter(users_organizations::org_uuid.eq(org_uuid))
+ .filter(
+ users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed))
+ )
+ .first::<UserOrganizationDb>(conn)
+ .ok().from_db()
+ }}
+ }
+
pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: {
users_organizations::table
@@ -701,7 +760,6 @@ impl UserOrganization {
mod tests {
use super::*;
#[test]
- #[allow(non_snake_case)]
fn partial_cmp_UserOrgType() {
assert!(UserOrgType::Owner > UserOrgType::Admin);
assert!(UserOrgType::Admin > UserOrgType::Manager);
diff --git a/src/db/models/user.rs b/src/db/models/user.rs
@@ -1,7 +1,7 @@
use crate::config;
use crate::crypto;
use crate::util;
-use chrono::{Duration, NaiveDateTime, Utc};
+use chrono::{NaiveDateTime, TimeDelta, Utc};
use diesel::result::{self, DatabaseErrorKind};
use serde_json::Value;
@@ -230,12 +230,12 @@ impl User {
routes: route_exception,
security_stamp: self.security_stamp.clone(),
expire: u64::try_from(
- (Utc::now()
- .naive_utc()
- .checked_add_signed(Duration::minutes(2)))
- .expect("Duration add overflowed")
- .and_utc()
- .timestamp(),
+ Utc::now()
+ .checked_add_signed(
+ TimeDelta::try_minutes(2).expect("TimeDelta::try_minutes(2) to work"),
+ )
+ .expect("overflow")
+ .timestamp(),
)
.expect("underflow"),
};
@@ -273,24 +273,26 @@ impl User {
UserStatus::Enabled
};
json!({
- "_Status": i32::from(status),
- "Id": self.uuid,
- "Name": self.name,
- "Email": self.email,
- "EmailVerified": true,
- "Premium": true,
- "MasterPasswordHint": self.password_hint,
- "Culture": "en-US",
- "TwoFactorEnabled": twofactor_enabled,
- "Key": self.akey,
- "PrivateKey": self.private_key,
- "SecurityStamp": self.security_stamp,
- "Organizations": orgs_json,
- "Providers": [],
- "ProviderOrganizations": [],
- "ForcePasswordReset": false,
- "AvatarColor": self.avatar_color,
- "Object": "profile",
+ "_status": i32::from(status),
+ "id": self.uuid,
+ "name": self.name,
+ "email": self.email,
+ "emailVerified": true,
+ "premium": true,
+ "premiumFromOrganization": false,
+ "masterPasswordHint": self.password_hint,
+ "culture": "en-US",
+ "twoFactorEnabled": twofactor_enabled,
+ "key": self.akey,
+ "privateKey": self.private_key,
+ "securityStamp": self.security_stamp,
+ "organizations": orgs_json,
+ "providers": [],
+ "providerOrganizations": [],
+ "forcePasswordReset": false,
+ "avatarColor": self.avatar_color,
+ "usesKeyConnector": false,
+ "object": "profile",
})
}
diff --git a/src/main.rs b/src/main.rs
@@ -65,7 +65,7 @@
// The more key/value pairs there are the more recursion occurs.
// We want to keep this as low as possible, but not higher then 128.
// If you go above 128 it will cause rust-analyzer to fail,
-#![recursion_limit = "103"]
+#![recursion_limit = "206"]
#[macro_use]
extern crate diesel;
#[macro_use]
diff --git a/src/static/global_domains.json b/src/static/global_domains.json
@@ -1,80 +1,80 @@
[
{
- "Type": 2,
- "Domains": [
+ "type": 2,
+ "domains": [
"ameritrade.com",
"tdameritrade.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 3,
- "Domains": [
+ "type": 3,
+ "domains": [
"bankofamerica.com",
"bofa.com",
"mbna.com",
"usecfo.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 4,
- "Domains": [
+ "type": 4,
+ "domains": [
"sprint.com",
"sprintpcs.com",
"nextel.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 0,
- "Domains": [
+ "type": 0,
+ "domains": [
"youtube.com",
"google.com",
"gmail.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 1,
- "Domains": [
+ "type": 1,
+ "domains": [
"apple.com",
"icloud.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 5,
- "Domains": [
+ "type": 5,
+ "domains": [
"wellsfargo.com",
"wf.com",
"wellsfargoadvisors.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 6,
- "Domains": [
+ "type": 6,
+ "domains": [
"mymerrill.com",
"ml.com",
"merrilledge.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 7,
- "Domains": [
+ "type": 7,
+ "domains": [
"accountonline.com",
"citi.com",
"citibank.com",
"citicards.com",
"citibankonline.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 8,
- "Domains": [
+ "type": 8,
+ "domains": [
"cnet.com",
"cnettv.com",
"com.com",
@@ -83,21 +83,21 @@
"search.com",
"upload.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 9,
- "Domains": [
+ "type": 9,
+ "domains": [
"bananarepublic.com",
"gap.com",
"oldnavy.com",
"piperlime.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 10,
- "Domains": [
+ "type": 10,
+ "domains": [
"bing.com",
"hotmail.com",
"live.com",
@@ -113,53 +113,53 @@
"azure.com",
"windowsazure.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 11,
- "Domains": [
+ "type": 11,
+ "domains": [
"ua2go.com",
"ual.com",
"united.com",
"unitedwifi.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 12,
- "Domains": [
+ "type": 12,
+ "domains": [
"overture.com",
"yahoo.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 13,
- "Domains": [
+ "type": 13,
+ "domains": [
"zonealarm.com",
"zonelabs.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 14,
- "Domains": [
+ "type": 14,
+ "domains": [
"paypal.com",
"paypal-search.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 15,
- "Domains": [
+ "type": 15,
+ "domains": [
"avon.com",
"youravon.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 16,
- "Domains": [
+ "type": 16,
+ "domains": [
"diapers.com",
"soap.com",
"wag.com",
@@ -172,19 +172,19 @@
"look.com",
"vinemarket.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 17,
- "Domains": [
+ "type": 17,
+ "domains": [
"1800contacts.com",
"800contacts.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 18,
- "Domains": [
+ "type": 18,
+ "domains": [
"amazon.com",
"amazon.com.be",
"amazon.ae",
@@ -205,240 +205,240 @@
"amazon.se",
"amazon.sg"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 19,
- "Domains": [
+ "type": 19,
+ "domains": [
"cox.com",
"cox.net",
"coxbusiness.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 20,
- "Domains": [
+ "type": 20,
+ "domains": [
"mynortonaccount.com",
"norton.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 21,
- "Domains": [
+ "type": 21,
+ "domains": [
"verizon.com",
"verizon.net"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 22,
- "Domains": [
+ "type": 22,
+ "domains": [
"rakuten.com",
"buy.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 23,
- "Domains": [
+ "type": 23,
+ "domains": [
"siriusxm.com",
"sirius.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 24,
- "Domains": [
+ "type": 24,
+ "domains": [
"ea.com",
"origin.com",
"play4free.com",
"tiberiumalliance.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 25,
- "Domains": [
+ "type": 25,
+ "domains": [
"37signals.com",
"basecamp.com",
"basecamphq.com",
"highrisehq.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 26,
- "Domains": [
+ "type": 26,
+ "domains": [
"steampowered.com",
"steamcommunity.com",
"steamgames.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 27,
- "Domains": [
+ "type": 27,
+ "domains": [
"chart.io",
"chartio.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 28,
- "Domains": [
+ "type": 28,
+ "domains": [
"gotomeeting.com",
"citrixonline.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 29,
- "Domains": [
+ "type": 29,
+ "domains": [
"gogoair.com",
"gogoinflight.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 30,
- "Domains": [
+ "type": 30,
+ "domains": [
"mysql.com",
"oracle.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 31,
- "Domains": [
+ "type": 31,
+ "domains": [
"discover.com",
"discovercard.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 32,
- "Domains": [
+ "type": 32,
+ "domains": [
"dcu.org",
"dcu-online.org"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 33,
- "Domains": [
+ "type": 33,
+ "domains": [
"healthcare.gov",
"cuidadodesalud.gov",
"cms.gov"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 34,
- "Domains": [
+ "type": 34,
+ "domains": [
"pepco.com",
"pepcoholdings.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 35,
- "Domains": [
+ "type": 35,
+ "domains": [
"century21.com",
"21online.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 36,
- "Domains": [
+ "type": 36,
+ "domains": [
"comcast.com",
"comcast.net",
"xfinity.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 37,
- "Domains": [
+ "type": 37,
+ "domains": [
"cricketwireless.com",
"aiowireless.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 38,
- "Domains": [
+ "type": 38,
+ "domains": [
"mandtbank.com",
"mtb.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 39,
- "Domains": [
+ "type": 39,
+ "domains": [
"dropbox.com",
"getdropbox.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 40,
- "Domains": [
+ "type": 40,
+ "domains": [
"snapfish.com",
"snapfish.ca"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 41,
- "Domains": [
+ "type": 41,
+ "domains": [
"alibaba.com",
"aliexpress.com",
"aliyun.com",
"net.cn"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 42,
- "Domains": [
+ "type": 42,
+ "domains": [
"playstation.com",
"sonyentertainmentnetwork.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 43,
- "Domains": [
+ "type": 43,
+ "domains": [
"mercadolivre.com",
"mercadolivre.com.br",
"mercadolibre.com",
"mercadolibre.com.ar",
"mercadolibre.com.mx"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 44,
- "Domains": [
+ "type": 44,
+ "domains": [
"zendesk.com",
"zopim.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 45,
- "Domains": [
+ "type": 45,
+ "domains": [
"autodesk.com",
"tinkercad.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 46,
- "Domains": [
+ "type": 46,
+ "domains": [
"railnation.ru",
"railnation.de",
"rail-nation.com",
@@ -447,152 +447,152 @@
"trucknation.de",
"traviangames.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 47,
- "Domains": [
+ "type": 47,
+ "domains": [
"wpcu.coop",
"wpcuonline.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 48,
- "Domains": [
+ "type": 48,
+ "domains": [
"mathletics.com",
"mathletics.com.au",
"mathletics.co.uk"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 49,
- "Domains": [
+ "type": 49,
+ "domains": [
"discountbank.co.il",
"telebank.co.il"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 50,
- "Domains": [
+ "type": 50,
+ "domains": [
"mi.com",
"xiaomi.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 52,
- "Domains": [
+ "type": 52,
+ "domains": [
"postepay.it",
"poste.it"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 51,
- "Domains": [
+ "type": 51,
+ "domains": [
"facebook.com",
"messenger.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 53,
- "Domains": [
+ "type": 53,
+ "domains": [
"skysports.com",
"skybet.com",
"skyvegas.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 54,
- "Domains": [
+ "type": 54,
+ "domains": [
"disneymoviesanywhere.com",
"go.com",
"disney.com",
"dadt.com",
"disneyplus.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 55,
- "Domains": [
+ "type": 55,
+ "domains": [
"pokemon-gl.com",
"pokemon.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 56,
- "Domains": [
+ "type": 56,
+ "domains": [
"myuv.com",
"uvvu.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 58,
- "Domains": [
+ "type": 58,
+ "domains": [
"mdsol.com",
"imedidata.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 57,
- "Domains": [
+ "type": 57,
+ "domains": [
"bank-yahav.co.il",
"bankhapoalim.co.il"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 59,
- "Domains": [
+ "type": 59,
+ "domains": [
"sears.com",
"shld.net"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 60,
- "Domains": [
+ "type": 60,
+ "domains": [
"xiami.com",
"alipay.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 61,
- "Domains": [
+ "type": 61,
+ "domains": [
"belkin.com",
"seedonk.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 62,
- "Domains": [
+ "type": 62,
+ "domains": [
"turbotax.com",
"intuit.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 63,
- "Domains": [
+ "type": 63,
+ "domains": [
"shopify.com",
"myshopify.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 64,
- "Domains": [
+ "type": 64,
+ "domains": [
"ebay.com",
"ebay.at",
"ebay.be",
@@ -617,53 +617,53 @@
"ebay.ph",
"ebay.pl"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 65,
- "Domains": [
+ "type": 65,
+ "domains": [
"techdata.com",
"techdata.ch"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 66,
- "Domains": [
+ "type": 66,
+ "domains": [
"schwab.com",
"schwabplan.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 68,
- "Domains": [
+ "type": 68,
+ "domains": [
"tesla.com",
"teslamotors.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 69,
- "Domains": [
+ "type": 69,
+ "domains": [
"morganstanley.com",
"morganstanleyclientserv.com",
"stockplanconnect.com",
"ms.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 70,
- "Domains": [
+ "type": 70,
+ "domains": [
"taxact.com",
"taxactonline.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 71,
- "Domains": [
+ "type": 71,
+ "domains": [
"mediawiki.org",
"wikibooks.org",
"wikidata.org",
@@ -676,11 +676,11 @@
"wikivoyage.org",
"wiktionary.org"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 72,
- "Domains": [
+ "type": 72,
+ "domains": [
"airbnb.at",
"airbnb.be",
"airbnb.ca",
@@ -735,11 +735,11 @@
"airbnb.ru",
"airbnb.se"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 73,
- "Domains": [
+ "type": 73,
+ "domains": [
"eventbrite.at",
"eventbrite.be",
"eventbrite.ca",
@@ -767,11 +767,11 @@
"eventbrite.se",
"eventbrite.sg"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 74,
- "Domains": [
+ "type": 74,
+ "domains": [
"stackexchange.com",
"superuser.com",
"stackoverflow.com",
@@ -780,19 +780,19 @@
"askubuntu.com",
"stackapps.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 75,
- "Domains": [
+ "type": 75,
+ "domains": [
"docusign.com",
"docusign.net"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 76,
- "Domains": [
+ "type": 76,
+ "domains": [
"envato.com",
"themeforest.net",
"codecanyon.net",
@@ -802,28 +802,28 @@
"photodune.net",
"3docean.net"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 77,
- "Domains": [
+ "type": 77,
+ "domains": [
"x10hosting.com",
"x10premium.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 78,
- "Domains": [
+ "type": 78,
+ "domains": [
"dnsomatic.com",
"opendns.com",
"umbrella.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 79,
- "Domains": [
+ "type": 79,
+ "domains": [
"cagreatamerica.com",
"canadaswonderland.com",
"carowinds.com",
@@ -838,36 +838,36 @@
"visitkingsisland.com",
"worldsoffun.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 80,
- "Domains": [
+ "type": 80,
+ "domains": [
"ubnt.com",
"ui.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 81,
- "Domains": [
+ "type": 81,
+ "domains": [
"discordapp.com",
"discord.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 82,
- "Domains": [
+ "type": 82,
+ "domains": [
"netcup.de",
"netcup.eu",
"customercontrolpanel.de"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 83,
- "Domains": [
+ "type": 83,
+ "domains": [
"yandex.com",
"ya.ru",
"yandex.az",
@@ -891,44 +891,44 @@
"yandex.ua",
"yandex.uz"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 84,
- "Domains": [
+ "type": 84,
+ "domains": [
"sonyentertainmentnetwork.com",
"sony.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 85,
- "Domains": [
+ "type": 85,
+ "domains": [
"proton.me",
"protonmail.com",
"protonvpn.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 86,
- "Domains": [
+ "type": 86,
+ "domains": [
"ubisoft.com",
"ubi.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 87,
- "Domains": [
+ "type": 87,
+ "domains": [
"transferwise.com",
"wise.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 88,
- "Domains": [
+ "type": 88,
+ "domains": [
"takeaway.com",
"just-eat.dk",
"just-eat.no",
@@ -939,11 +939,11 @@
"thuisbezorgd.nl",
"pyszne.pl"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 89,
- "Domains": [
+ "type": 89,
+ "domains": [
"atlassian.com",
"bitbucket.org",
"trello.com",
@@ -951,11 +951,11 @@
"atlassian.net",
"jira.com"
],
- "Excluded": false
+ "excluded": false
},
{
- "Type": 90,
- "Domains": [
+ "type": 90,
+ "domains": [
"pinterest.com",
"pinterest.com.au",
"pinterest.cl",
@@ -970,6 +970,6 @@
"pinterest.pt",
"pinterest.se"
],
- "Excluded": false
+ "excluded": false
}
-]
-\ No newline at end of file
+]
diff --git a/src/static/scripts/bootstrap.bundle.js b/src/static/scripts/bootstrap.bundle.js
@@ -1,6 +1,6 @@
/*!
- * Bootstrap v5.3.1 (https://getbootstrap.com/)
- * Copyright 2011-2023 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
+ * Bootstrap v5.3.3 (https://getbootstrap.com/)
+ * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
@@ -210,7 +210,6 @@
const reflow = element => {
element.offsetHeight; // eslint-disable-line no-unused-expressions
};
-
const getjQuery = () => {
if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {
return window.jQuery;
@@ -648,7 +647,7 @@
* Constants
*/
- const VERSION = '5.3.1';
+ const VERSION = '5.3.3';
/**
* Class definition
@@ -731,7 +730,7 @@
}
selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null;
}
- return parseSelector(selector);
+ return selector ? selector.split(',').map(sel => parseSelector(sel)).join(',') : null;
};
const SelectorEngine = {
find(selector, element = document.documentElement) {
@@ -3916,7 +3915,6 @@
// if false, we use the backdrop helper without adding any element to the dom
rootElement: 'body' // give the choice to place backdrop under different elements
};
-
const DefaultType$8 = {
className: 'string',
clickCallback: '(function|null)',
@@ -4041,7 +4039,6 @@
autofocus: true,
trapElement: null // The element to trap focus inside of
};
-
const DefaultType$7 = {
autofocus: 'boolean',
trapElement: 'element'
@@ -4768,7 +4765,10 @@
br: [],
col: [],
code: [],
+ dd: [],
div: [],
+ dl: [],
+ dt: [],
em: [],
hr: [],
h1: [],
@@ -5866,7 +5866,7 @@
const CLASS_DROPDOWN = 'dropdown';
const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle';
const SELECTOR_DROPDOWN_MENU = '.dropdown-menu';
- const NOT_SELECTOR_DROPDOWN_TOGGLE = ':not(.dropdown-toggle)';
+ const NOT_SELECTOR_DROPDOWN_TOGGLE = `:not(${SELECTOR_DROPDOWN_TOGGLE})`;
const SELECTOR_TAB_PANEL = '.list-group, .nav, [role="tablist"]';
const SELECTOR_OUTER = '.nav-item, .list-group-item';
const SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role="tab"]${NOT_SELECTOR_DROPDOWN_TOGGLE}`;
@@ -6311,3 +6311,4 @@
return index_umd;
}));
+//# sourceMappingURL=bootstrap.bundle.js.map
diff --git a/src/static/scripts/bootstrap.css b/src/static/scripts/bootstrap.css
@@ -1,7 +1,7 @@
@charset "UTF-8";
/*!
- * Bootstrap v5.3.1 (https://getbootstrap.com/)
- * Copyright 2011-2023 The Bootstrap Authors
+ * Bootstrap v5.3.3 (https://getbootstrap.com/)
+ * Copyright 2011-2024 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
@@ -99,6 +99,7 @@
--bs-link-hover-color: #0a58ca;
--bs-link-hover-color-rgb: 10, 88, 202;
--bs-code-color: #d63384;
+ --bs-highlight-color: #212529;
--bs-highlight-bg: #fff3cd;
--bs-border-width: 1px;
--bs-border-style: solid;
@@ -170,6 +171,8 @@
--bs-link-color-rgb: 110, 168, 254;
--bs-link-hover-color-rgb: 139, 185, 254;
--bs-code-color: #e685b5;
+ --bs-highlight-color: #dee2e6;
+ --bs-highlight-bg: #664d03;
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #75b798;
@@ -325,6 +328,7 @@ small, .small {
mark, .mark {
padding: 0.1875em;
+ color: var(--bs-highlight-color);
background-color: var(--bs-highlight-bg);
}
@@ -819,7 +823,7 @@ progress {
.row-cols-3 > * {
flex: 0 0 auto;
- width: 33.3333333333%;
+ width: 33.33333333%;
}
.row-cols-4 > * {
@@ -834,7 +838,7 @@ progress {
.row-cols-6 > * {
flex: 0 0 auto;
- width: 16.6666666667%;
+ width: 16.66666667%;
}
.col-auto {
@@ -1024,7 +1028,7 @@ progress {
}
.row-cols-sm-3 > * {
flex: 0 0 auto;
- width: 33.3333333333%;
+ width: 33.33333333%;
}
.row-cols-sm-4 > * {
flex: 0 0 auto;
@@ -1036,7 +1040,7 @@ progress {
}
.row-cols-sm-6 > * {
flex: 0 0 auto;
- width: 16.6666666667%;
+ width: 16.66666667%;
}
.col-sm-auto {
flex: 0 0 auto;
@@ -1193,7 +1197,7 @@ progress {
}
.row-cols-md-3 > * {
flex: 0 0 auto;
- width: 33.3333333333%;
+ width: 33.33333333%;
}
.row-cols-md-4 > * {
flex: 0 0 auto;
@@ -1205,7 +1209,7 @@ progress {
}
.row-cols-md-6 > * {
flex: 0 0 auto;
- width: 16.6666666667%;
+ width: 16.66666667%;
}
.col-md-auto {
flex: 0 0 auto;
@@ -1362,7 +1366,7 @@ progress {
}
.row-cols-lg-3 > * {
flex: 0 0 auto;
- width: 33.3333333333%;
+ width: 33.33333333%;
}
.row-cols-lg-4 > * {
flex: 0 0 auto;
@@ -1374,7 +1378,7 @@ progress {
}
.row-cols-lg-6 > * {
flex: 0 0 auto;
- width: 16.6666666667%;
+ width: 16.66666667%;
}
.col-lg-auto {
flex: 0 0 auto;
@@ -1531,7 +1535,7 @@ progress {
}
.row-cols-xl-3 > * {
flex: 0 0 auto;
- width: 33.3333333333%;
+ width: 33.33333333%;
}
.row-cols-xl-4 > * {
flex: 0 0 auto;
@@ -1543,7 +1547,7 @@ progress {
}
.row-cols-xl-6 > * {
flex: 0 0 auto;
- width: 16.6666666667%;
+ width: 16.66666667%;
}
.col-xl-auto {
flex: 0 0 auto;
@@ -1700,7 +1704,7 @@ progress {
}
.row-cols-xxl-3 > * {
flex: 0 0 auto;
- width: 33.3333333333%;
+ width: 33.33333333%;
}
.row-cols-xxl-4 > * {
flex: 0 0 auto;
@@ -1712,7 +1716,7 @@ progress {
}
.row-cols-xxl-6 > * {
flex: 0 0 auto;
- width: 16.6666666667%;
+ width: 16.66666667%;
}
.col-xxl-auto {
flex: 0 0 auto;
@@ -1856,16 +1860,16 @@ progress {
--bs-table-bg-type: initial;
--bs-table-color-state: initial;
--bs-table-bg-state: initial;
- --bs-table-color: var(--bs-body-color);
+ --bs-table-color: var(--bs-emphasis-color);
--bs-table-bg: var(--bs-body-bg);
--bs-table-border-color: var(--bs-border-color);
--bs-table-accent-bg: transparent;
- --bs-table-striped-color: var(--bs-body-color);
- --bs-table-striped-bg: rgba(0, 0, 0, 0.05);
- --bs-table-active-color: var(--bs-body-color);
- --bs-table-active-bg: rgba(0, 0, 0, 0.1);
- --bs-table-hover-color: var(--bs-body-color);
- --bs-table-hover-bg: rgba(0, 0, 0, 0.075);
+ --bs-table-striped-color: var(--bs-emphasis-color);
+ --bs-table-striped-bg: rgba(var(--bs-emphasis-color-rgb), 0.05);
+ --bs-table-active-color: var(--bs-emphasis-color);
+ --bs-table-active-bg: rgba(var(--bs-emphasis-color-rgb), 0.1);
+ --bs-table-hover-color: var(--bs-emphasis-color);
+ --bs-table-hover-bg: rgba(var(--bs-emphasis-color-rgb), 0.075);
width: 100%;
margin-bottom: 1rem;
vertical-align: top;
@@ -1934,7 +1938,7 @@ progress {
.table-primary {
--bs-table-color: #000;
--bs-table-bg: #cfe2ff;
- --bs-table-border-color: #bacbe6;
+ --bs-table-border-color: #a6b5cc;
--bs-table-striped-bg: #c5d7f2;
--bs-table-striped-color: #000;
--bs-table-active-bg: #bacbe6;
@@ -1948,7 +1952,7 @@ progress {
.table-secondary {
--bs-table-color: #000;
--bs-table-bg: #e2e3e5;
- --bs-table-border-color: #cbccce;
+ --bs-table-border-color: #b5b6b7;
--bs-table-striped-bg: #d7d8da;
--bs-table-striped-color: #000;
--bs-table-active-bg: #cbccce;
@@ -1962,7 +1966,7 @@ progress {
.table-success {
--bs-table-color: #000;
--bs-table-bg: #d1e7dd;
- --bs-table-border-color: #bcd0c7;
+ --bs-table-border-color: #a7b9b1;
--bs-table-striped-bg: #c7dbd2;
--bs-table-striped-color: #000;
--bs-table-active-bg: #bcd0c7;
@@ -1976,7 +1980,7 @@ progress {
.table-info {
--bs-table-color: #000;
--bs-table-bg: #cff4fc;
- --bs-table-border-color: #badce3;
+ --bs-table-border-color: #a6c3ca;
--bs-table-striped-bg: #c5e8ef;
--bs-table-striped-color: #000;
--bs-table-active-bg: #badce3;
@@ -1990,7 +1994,7 @@ progress {
.table-warning {
--bs-table-color: #000;
--bs-table-bg: #fff3cd;
- --bs-table-border-color: #e6dbb9;
+ --bs-table-border-color: #ccc2a4;
--bs-table-striped-bg: #f2e7c3;
--bs-table-striped-color: #000;
--bs-table-active-bg: #e6dbb9;
@@ -2004,7 +2008,7 @@ progress {
.table-danger {
--bs-table-color: #000;
--bs-table-bg: #f8d7da;
- --bs-table-border-color: #dfc2c4;
+ --bs-table-border-color: #c6acae;
--bs-table-striped-bg: #eccccf;
--bs-table-striped-color: #000;
--bs-table-active-bg: #dfc2c4;
@@ -2018,7 +2022,7 @@ progress {
.table-light {
--bs-table-color: #000;
--bs-table-bg: #f8f9fa;
- --bs-table-border-color: #dfe0e1;
+ --bs-table-border-color: #c6c7c8;
--bs-table-striped-bg: #ecedee;
--bs-table-striped-color: #000;
--bs-table-active-bg: #dfe0e1;
@@ -2032,7 +2036,7 @@ progress {
.table-dark {
--bs-table-color: #fff;
--bs-table-bg: #212529;
- --bs-table-border-color: #373b3e;
+ --bs-table-border-color: #4d5154;
--bs-table-striped-bg: #2c3034;
--bs-table-striped-color: #fff;
--bs-table-active-bg: #373b3e;
@@ -2388,6 +2392,7 @@ textarea.form-control-lg {
.form-check-input {
--bs-form-check-bg: var(--bs-body-bg);
+ flex-shrink: 0;
width: 1em;
height: 1em;
margin-top: 0.25em;
@@ -2544,7 +2549,7 @@ textarea.form-control-lg {
height: 0.5rem;
color: transparent;
cursor: pointer;
- background-color: var(--bs-tertiary-bg);
+ background-color: var(--bs-secondary-bg);
border-color: transparent;
border-radius: 1rem;
}
@@ -2573,7 +2578,7 @@ textarea.form-control-lg {
height: 0.5rem;
color: transparent;
cursor: pointer;
- background-color: var(--bs-tertiary-bg);
+ background-color: var(--bs-secondary-bg);
border-color: transparent;
border-radius: 1rem;
}
@@ -3037,6 +3042,9 @@ textarea.form-control-lg {
.btn-check:checked + .btn:focus-visible, :not(.btn-check) + .btn:active:focus-visible, .btn:first-child:active:focus-visible, .btn.active:focus-visible, .btn.show:focus-visible {
box-shadow: var(--bs-btn-focus-box-shadow);
}
+.btn-check:checked:focus-visible + .btn {
+ box-shadow: var(--bs-btn-focus-box-shadow);
+}
.btn:disabled, .btn.disabled, fieldset:disabled .btn {
color: var(--bs-btn-disabled-color);
pointer-events: none;
@@ -3431,7 +3439,7 @@ textarea.form-control-lg {
--bs-dropdown-inner-border-radius: calc(var(--bs-border-radius) - var(--bs-border-width));
--bs-dropdown-divider-bg: var(--bs-border-color-translucent);
--bs-dropdown-divider-margin-y: 0.5rem;
- --bs-dropdown-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
+ --bs-dropdown-box-shadow: var(--bs-box-shadow);
--bs-dropdown-link-color: var(--bs-body-color);
--bs-dropdown-link-hover-color: var(--bs-body-color);
--bs-dropdown-link-hover-bg: var(--bs-tertiary-bg);
@@ -4568,12 +4576,11 @@ textarea.form-control-lg {
--bs-accordion-btn-padding-y: 1rem;
--bs-accordion-btn-color: var(--bs-body-color);
--bs-accordion-btn-bg: var(--bs-accordion-bg);
- --bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
+ --bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23212529' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");
--bs-accordion-btn-icon-width: 1.25rem;
--bs-accordion-btn-icon-transform: rotate(-180deg);
--bs-accordion-btn-icon-transition: transform 0.2s ease-in-out;
- --bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23052c65'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
- --bs-accordion-btn-focus-border-color: #86b7fe;
+ --bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23052c65' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");
--bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
--bs-accordion-body-padding-x: 1.25rem;
--bs-accordion-body-padding-y: 1rem;
@@ -4631,7 +4638,6 @@ textarea.form-control-lg {
}
.accordion-button:focus {
z-index: 3;
- border-color: var(--bs-accordion-btn-focus-border-color);
outline: 0;
box-shadow: var(--bs-accordion-btn-focus-box-shadow);
}
@@ -4649,7 +4655,7 @@ textarea.form-control-lg {
border-top-left-radius: var(--bs-accordion-border-radius);
border-top-right-radius: var(--bs-accordion-border-radius);
}
-.accordion-item:first-of-type .accordion-button {
+.accordion-item:first-of-type > .accordion-header .accordion-button {
border-top-left-radius: var(--bs-accordion-inner-border-radius);
border-top-right-radius: var(--bs-accordion-inner-border-radius);
}
@@ -4660,11 +4666,11 @@ textarea.form-control-lg {
border-bottom-right-radius: var(--bs-accordion-border-radius);
border-bottom-left-radius: var(--bs-accordion-border-radius);
}
-.accordion-item:last-of-type .accordion-button.collapsed {
+.accordion-item:last-of-type > .accordion-header .accordion-button.collapsed {
border-bottom-right-radius: var(--bs-accordion-inner-border-radius);
border-bottom-left-radius: var(--bs-accordion-inner-border-radius);
}
-.accordion-item:last-of-type .accordion-collapse {
+.accordion-item:last-of-type > .accordion-collapse {
border-bottom-right-radius: var(--bs-accordion-border-radius);
border-bottom-left-radius: var(--bs-accordion-border-radius);
}
@@ -4673,21 +4679,21 @@ textarea.form-control-lg {
padding: var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x);
}
-.accordion-flush .accordion-collapse {
- border-width: 0;
-}
-.accordion-flush .accordion-item {
+.accordion-flush > .accordion-item {
border-right: 0;
border-left: 0;
border-radius: 0;
}
-.accordion-flush .accordion-item:first-child {
+.accordion-flush > .accordion-item:first-child {
border-top: 0;
}
-.accordion-flush .accordion-item:last-child {
+.accordion-flush > .accordion-item:last-child {
border-bottom: 0;
}
-.accordion-flush .accordion-item .accordion-button, .accordion-flush .accordion-item .accordion-button.collapsed {
+.accordion-flush > .accordion-item > .accordion-header .accordion-button, .accordion-flush > .accordion-item > .accordion-header .accordion-button.collapsed {
+ border-radius: 0;
+}
+.accordion-flush > .accordion-item > .accordion-collapse {
border-radius: 0;
}
@@ -5473,7 +5479,7 @@ textarea.form-control-lg {
--bs-modal-border-color: var(--bs-border-color-translucent);
--bs-modal-border-width: var(--bs-border-width);
--bs-modal-border-radius: var(--bs-border-radius-lg);
- --bs-modal-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
+ --bs-modal-box-shadow: var(--bs-box-shadow-sm);
--bs-modal-inner-border-radius: calc(var(--bs-border-radius-lg) - (var(--bs-border-width)));
--bs-modal-header-padding-x: 1rem;
--bs-modal-header-padding-y: 1rem;
@@ -5573,7 +5579,6 @@ textarea.form-control-lg {
display: flex;
flex-shrink: 0;
align-items: center;
- justify-content: space-between;
padding: var(--bs-modal-header-padding);
border-bottom: var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);
border-top-left-radius: var(--bs-modal-inner-border-radius);
@@ -5614,7 +5619,7 @@ textarea.form-control-lg {
@media (min-width: 576px) {
.modal {
--bs-modal-margin: 1.75rem;
- --bs-modal-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
+ --bs-modal-box-shadow: var(--bs-box-shadow);
}
.modal-dialog {
max-width: var(--bs-modal-width);
@@ -5866,7 +5871,7 @@ textarea.form-control-lg {
--bs-popover-border-color: var(--bs-border-color-translucent);
--bs-popover-border-radius: var(--bs-border-radius-lg);
--bs-popover-inner-border-radius: calc(var(--bs-border-radius-lg) - var(--bs-border-width));
- --bs-popover-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
+ --bs-popover-box-shadow: var(--bs-box-shadow);
--bs-popover-header-padding-x: 1rem;
--bs-popover-header-padding-y: 0.5rem;
--bs-popover-header-font-size: 1rem;
@@ -6139,20 +6144,12 @@ textarea.form-control-lg {
background-size: 100% 100%;
}
-/* rtl:options: {
- "autoRename": true,
- "stringMap":[ {
- "name" : "prev-next",
- "search" : "prev",
- "replace" : "next"
- } ]
-} */
.carousel-control-prev-icon {
- background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e");
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")*/;
}
.carousel-control-next-icon {
- background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")*/;
}
.carousel-indicators {
@@ -6301,7 +6298,7 @@ textarea.form-control-lg {
--bs-offcanvas-bg: var(--bs-body-bg);
--bs-offcanvas-border-width: var(--bs-border-width);
--bs-offcanvas-border-color: var(--bs-border-color-translucent);
- --bs-offcanvas-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
+ --bs-offcanvas-box-shadow: var(--bs-box-shadow-sm);
--bs-offcanvas-transition: transform 0.3s ease-in-out;
--bs-offcanvas-title-line-height: 1.5;
}
@@ -6772,14 +6769,11 @@ textarea.form-control-lg {
.offcanvas-header {
display: flex;
align-items: center;
- justify-content: space-between;
padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);
}
.offcanvas-header .btn-close {
padding: calc(var(--bs-offcanvas-padding-y) * 0.5) calc(var(--bs-offcanvas-padding-x) * 0.5);
- margin-top: calc(-0.5 * var(--bs-offcanvas-padding-y));
- margin-right: calc(-0.5 * var(--bs-offcanvas-padding-x));
- margin-bottom: calc(-0.5 * var(--bs-offcanvas-padding-y));
+ margin: calc(-0.5 * var(--bs-offcanvas-padding-y)) calc(-0.5 * var(--bs-offcanvas-padding-x)) calc(-0.5 * var(--bs-offcanvas-padding-y)) auto;
}
.offcanvas-title {
@@ -7380,15 +7374,15 @@ textarea.form-control-lg {
}
.shadow {
- box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
+ box-shadow: var(--bs-box-shadow) !important;
}
.shadow-sm {
- box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;
+ box-shadow: var(--bs-box-shadow-sm) !important;
}
.shadow-lg {
- box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important;
+ box-shadow: var(--bs-box-shadow-lg) !important;
}
.shadow-none {
@@ -12059,3 +12053,5 @@ textarea.form-control-lg {
display: none !important;
}
}
+
+/*# sourceMappingURL=bootstrap.css.map */
+\ No newline at end of file
diff --git a/src/static/scripts/datatables.css b/src/static/scripts/datatables.css
@@ -4,10 +4,10 @@
*
* To rebuild or modify this file with the latest versions of the included
* software please visit:
- * https://datatables.net/download/#bs5/dt-1.13.6
+ * https://datatables.net/download/#bs5/dt-2.0.8
*
* Included libraries:
- * DataTables 1.13.6
+ * DataTables 2.0.8
*/
@charset "UTF-8";
@@ -30,76 +30,124 @@ table.dataTable td.dt-control {
}
table.dataTable td.dt-control:before {
display: inline-block;
- color: rgba(0, 0, 0, 0.5);
- content: "â–º";
+ box-sizing: border-box;
+ content: "";
+ border-top: 5px solid transparent;
+ border-left: 10px solid rgba(0, 0, 0, 0.5);
+ border-bottom: 5px solid transparent;
+ border-right: 0px solid transparent;
}
table.dataTable tr.dt-hasChild td.dt-control:before {
- content: "â–¼";
+ border-top: 10px solid rgba(0, 0, 0, 0.5);
+ border-left: 5px solid transparent;
+ border-bottom: 0px solid transparent;
+ border-right: 5px solid transparent;
}
-html.dark table.dataTable td.dt-control:before {
- color: rgba(255, 255, 255, 0.5);
+html.dark table.dataTable td.dt-control:before,
+:root[data-bs-theme=dark] table.dataTable td.dt-control:before {
+ border-left-color: rgba(255, 255, 255, 0.5);
}
-html.dark table.dataTable tr.dt-hasChild td.dt-control:before {
- color: rgba(255, 255, 255, 0.5);
+html.dark table.dataTable tr.dt-hasChild td.dt-control:before,
+:root[data-bs-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before {
+ border-top-color: rgba(255, 255, 255, 0.5);
+ border-left-color: transparent;
}
-table.dataTable thead > tr > th.sorting, table.dataTable thead > tr > th.sorting_asc, table.dataTable thead > tr > th.sorting_desc, table.dataTable thead > tr > th.sorting_asc_disabled, table.dataTable thead > tr > th.sorting_desc_disabled,
-table.dataTable thead > tr > td.sorting,
-table.dataTable thead > tr > td.sorting_asc,
-table.dataTable thead > tr > td.sorting_desc,
-table.dataTable thead > tr > td.sorting_asc_disabled,
-table.dataTable thead > tr > td.sorting_desc_disabled {
- cursor: pointer;
- position: relative;
- padding-right: 26px;
-}
-table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:after,
-table.dataTable thead > tr > td.sorting:before,
-table.dataTable thead > tr > td.sorting:after,
-table.dataTable thead > tr > td.sorting_asc:before,
-table.dataTable thead > tr > td.sorting_asc:after,
-table.dataTable thead > tr > td.sorting_desc:before,
-table.dataTable thead > tr > td.sorting_desc:after,
-table.dataTable thead > tr > td.sorting_asc_disabled:before,
-table.dataTable thead > tr > td.sorting_asc_disabled:after,
-table.dataTable thead > tr > td.sorting_desc_disabled:before,
-table.dataTable thead > tr > td.sorting_desc_disabled:after {
+div.dt-scroll-body thead tr,
+div.dt-scroll-body tfoot tr {
+ height: 0;
+}
+div.dt-scroll-body thead tr th, div.dt-scroll-body thead tr td,
+div.dt-scroll-body tfoot tr th,
+div.dt-scroll-body tfoot tr td {
+ height: 0 !important;
+ padding-top: 0px !important;
+ padding-bottom: 0px !important;
+ border-top-width: 0px !important;
+ border-bottom-width: 0px !important;
+}
+div.dt-scroll-body thead tr th div.dt-scroll-sizing, div.dt-scroll-body thead tr td div.dt-scroll-sizing,
+div.dt-scroll-body tfoot tr th div.dt-scroll-sizing,
+div.dt-scroll-body tfoot tr td div.dt-scroll-sizing {
+ height: 0 !important;
+ overflow: hidden !important;
+}
+
+table.dataTable thead > tr > th:active,
+table.dataTable thead > tr > td:active {
+ outline: none;
+}
+table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before,
+table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before,
+table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before {
position: absolute;
display: block;
- opacity: 0.125;
- right: 10px;
- line-height: 9px;
- font-size: 0.8em;
-}
-table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:before,
-table.dataTable thead > tr > td.sorting:before,
-table.dataTable thead > tr > td.sorting_asc:before,
-table.dataTable thead > tr > td.sorting_desc:before,
-table.dataTable thead > tr > td.sorting_asc_disabled:before,
-table.dataTable thead > tr > td.sorting_desc_disabled:before {
bottom: 50%;
content: "â–²";
content: "â–²"/"";
}
-table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:after,
-table.dataTable thead > tr > td.sorting:after,
-table.dataTable thead > tr > td.sorting_asc:after,
-table.dataTable thead > tr > td.sorting_desc:after,
-table.dataTable thead > tr > td.sorting_asc_disabled:after,
-table.dataTable thead > tr > td.sorting_desc_disabled:after {
+table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after,
+table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after,
+table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after {
+ position: absolute;
+ display: block;
top: 50%;
content: "â–¼";
content: "â–¼"/"";
}
-table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:after,
-table.dataTable thead > tr > td.sorting_asc:before,
-table.dataTable thead > tr > td.sorting_desc:after {
+table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc, table.dataTable thead > tr > th.dt-ordering-asc, table.dataTable thead > tr > th.dt-ordering-desc,
+table.dataTable thead > tr > td.dt-orderable-asc,
+table.dataTable thead > tr > td.dt-orderable-desc,
+table.dataTable thead > tr > td.dt-ordering-asc,
+table.dataTable thead > tr > td.dt-ordering-desc {
+ position: relative;
+ padding-right: 30px;
+}
+table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order,
+table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order,
+table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order,
+table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order,
+table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order {
+ position: absolute;
+ right: 12px;
+ top: 0;
+ bottom: 0;
+ width: 12px;
+}
+table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after,
+table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before,
+table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:after,
+table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:before,
+table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after,
+table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before,
+table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:after,
+table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:before,
+table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after {
+ left: 0;
+ opacity: 0.125;
+ line-height: 9px;
+ font-size: 0.8em;
+}
+table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc,
+table.dataTable thead > tr > td.dt-orderable-asc,
+table.dataTable thead > tr > td.dt-orderable-desc {
+ cursor: pointer;
+}
+table.dataTable thead > tr > th.dt-orderable-asc:hover, table.dataTable thead > tr > th.dt-orderable-desc:hover,
+table.dataTable thead > tr > td.dt-orderable-asc:hover,
+table.dataTable thead > tr > td.dt-orderable-desc:hover {
+ outline: 2px solid rgba(0, 0, 0, 0.05);
+ outline-offset: -2px;
+}
+table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after,
+table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before,
+table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after {
opacity: 0.6;
}
-table.dataTable thead > tr > th.sorting_desc_disabled:after, table.dataTable thead > tr > th.sorting_asc_disabled:before,
-table.dataTable thead > tr > td.sorting_desc_disabled:after,
-table.dataTable thead > tr > td.sorting_asc_disabled:before {
+table.dataTable thead > tr > th.sorting_desc_disabled span.dt-column-order:after, table.dataTable thead > tr > th.sorting_asc_disabled span.dt-column-order:before,
+table.dataTable thead > tr > td.sorting_desc_disabled span.dt-column-order:after,
+table.dataTable thead > tr > td.sorting_asc_disabled span.dt-column-order:before {
display: none;
}
table.dataTable thead > tr > th:active,
@@ -107,29 +155,39 @@ table.dataTable thead > tr > td:active {
outline: none;
}
-div.dataTables_scrollBody > table.dataTable > thead > tr > th:before, div.dataTables_scrollBody > table.dataTable > thead > tr > th:after,
-div.dataTables_scrollBody > table.dataTable > thead > tr > td:before,
-div.dataTables_scrollBody > table.dataTable > thead > tr > td:after {
- display: none;
+div.dt-scroll-body > table.dataTable > thead > tr > th,
+div.dt-scroll-body > table.dataTable > thead > tr > td {
+ overflow: hidden;
+}
+
+:root.dark table.dataTable thead > tr > th.dt-orderable-asc:hover, :root.dark table.dataTable thead > tr > th.dt-orderable-desc:hover,
+:root.dark table.dataTable thead > tr > td.dt-orderable-asc:hover,
+:root.dark table.dataTable thead > tr > td.dt-orderable-desc:hover,
+:root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-asc:hover,
+:root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-desc:hover,
+:root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-asc:hover,
+:root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-desc:hover {
+ outline: 2px solid rgba(255, 255, 255, 0.05);
}
-div.dataTables_processing {
+div.dt-processing {
position: absolute;
top: 50%;
left: 50%;
width: 200px;
margin-left: -100px;
- margin-top: -26px;
+ margin-top: -22px;
text-align: center;
padding: 2px;
+ z-index: 10;
}
-div.dataTables_processing > div:last-child {
+div.dt-processing > div:last-child {
position: relative;
width: 80px;
height: 15px;
margin: 1em auto;
}
-div.dataTables_processing > div:last-child > div {
+div.dt-processing > div:last-child > div {
position: absolute;
top: 0;
width: 13px;
@@ -139,19 +197,19 @@ div.dataTables_processing > div:last-child > div {
background: rgb(var(--dt-row-selected));
animation-timing-function: cubic-bezier(0, 1, 1, 0);
}
-div.dataTables_processing > div:last-child > div:nth-child(1) {
+div.dt-processing > div:last-child > div:nth-child(1) {
left: 8px;
animation: datatables-loader-1 0.6s infinite;
}
-div.dataTables_processing > div:last-child > div:nth-child(2) {
+div.dt-processing > div:last-child > div:nth-child(2) {
left: 8px;
animation: datatables-loader-2 0.6s infinite;
}
-div.dataTables_processing > div:last-child > div:nth-child(3) {
+div.dt-processing > div:last-child > div:nth-child(3) {
left: 32px;
animation: datatables-loader-2 0.6s infinite;
}
-div.dataTables_processing > div:last-child > div:nth-child(4) {
+div.dt-processing > div:last-child > div:nth-child(4) {
left: 56px;
animation: datatables-loader-3 0.6s infinite;
}
@@ -183,13 +241,16 @@ div.dataTables_processing > div:last-child > div:nth-child(4) {
table.dataTable.nowrap th, table.dataTable.nowrap td {
white-space: nowrap;
}
+table.dataTable th,
+table.dataTable td {
+ box-sizing: border-box;
+}
table.dataTable th.dt-left,
table.dataTable td.dt-left {
text-align: left;
}
table.dataTable th.dt-center,
-table.dataTable td.dt-center,
-table.dataTable td.dataTables_empty {
+table.dataTable td.dt-center {
text-align: center;
}
table.dataTable th.dt-right,
@@ -204,6 +265,16 @@ table.dataTable th.dt-nowrap,
table.dataTable td.dt-nowrap {
white-space: nowrap;
}
+table.dataTable th.dt-empty,
+table.dataTable td.dt-empty {
+ text-align: center;
+ vertical-align: top;
+}
+table.dataTable th.dt-type-numeric, table.dataTable th.dt-type-date,
+table.dataTable td.dt-type-numeric,
+table.dataTable td.dt-type-date {
+ text-align: right;
+}
table.dataTable thead th,
table.dataTable thead td,
table.dataTable tfoot th,
@@ -266,179 +337,158 @@ table.dataTable tbody td.dt-body-nowrap {
* ©2020 SpryMedia Ltd, all rights reserved.
* License: MIT datatables.net/license/mit
*/
-table.dataTable {
+table.table.dataTable {
clear: both;
- margin-top: 6px !important;
- margin-bottom: 6px !important;
- max-width: none !important;
- border-collapse: separate !important;
+ margin-bottom: 0;
+ max-width: none;
border-spacing: 0;
}
-table.dataTable td,
-table.dataTable th {
- -webkit-box-sizing: content-box;
- box-sizing: content-box;
-}
-table.dataTable td.dataTables_empty,
-table.dataTable th.dataTables_empty {
- text-align: center;
-}
-table.dataTable.nowrap th,
-table.dataTable.nowrap td {
- white-space: nowrap;
-}
-table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * {
+table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * {
box-shadow: none;
}
-table.dataTable > tbody > tr {
+table.table.dataTable > :not(caption) > * > * {
+ background-color: var(--bs-table-bg);
+}
+table.table.dataTable > tbody > tr {
background-color: transparent;
}
-table.dataTable > tbody > tr.selected > * {
+table.table.dataTable > tbody > tr.selected > * {
box-shadow: inset 0 0 0 9999px rgb(13, 110, 253);
box-shadow: inset 0 0 0 9999px rgb(var(--dt-row-selected));
color: rgb(255, 255, 255);
color: rgb(var(--dt-row-selected-text));
}
-table.dataTable > tbody > tr.selected a {
+table.table.dataTable > tbody > tr.selected a {
color: rgb(9, 10, 11);
color: rgb(var(--dt-row-selected-link));
}
-table.dataTable.table-striped > tbody > tr.odd > * {
+table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * {
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-stripe), 0.05);
}
-table.dataTable.table-striped > tbody > tr.odd.selected > * {
+table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1).selected > * {
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.95);
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.95);
}
-table.dataTable.table-hover > tbody > tr:hover > * {
+table.table.dataTable.table-hover > tbody > tr:hover > * {
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.075);
}
-table.dataTable.table-hover > tbody > tr.selected:hover > * {
+table.table.dataTable.table-hover > tbody > tr.selected:hover > * {
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.975);
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.975);
}
-div.dataTables_wrapper div.dataTables_length label {
+div.dt-container div.dt-length label {
font-weight: normal;
text-align: left;
white-space: nowrap;
}
-div.dataTables_wrapper div.dataTables_length select {
+div.dt-container div.dt-length select {
width: auto;
display: inline-block;
+ margin-right: 0.5em;
}
-div.dataTables_wrapper div.dataTables_filter {
+div.dt-container div.dt-search {
text-align: right;
}
-div.dataTables_wrapper div.dataTables_filter label {
+div.dt-container div.dt-search label {
font-weight: normal;
white-space: nowrap;
text-align: left;
}
-div.dataTables_wrapper div.dataTables_filter input {
+div.dt-container div.dt-search input {
margin-left: 0.5em;
display: inline-block;
width: auto;
}
-div.dataTables_wrapper div.dataTables_info {
+div.dt-container div.dt-info {
padding-top: 0.85em;
}
-div.dataTables_wrapper div.dataTables_paginate {
+div.dt-container div.dt-paging {
margin: 0;
- white-space: nowrap;
- text-align: right;
}
-div.dataTables_wrapper div.dataTables_paginate ul.pagination {
+div.dt-container div.dt-paging ul.pagination {
margin: 2px 0;
- white-space: nowrap;
- justify-content: flex-end;
+ flex-wrap: wrap;
}
-div.dataTables_wrapper div.dt-row {
+div.dt-container div.dt-row {
position: relative;
}
-div.dataTables_scrollHead table.dataTable {
+div.dt-scroll-head table.dataTable {
margin-bottom: 0 !important;
}
-div.dataTables_scrollBody > table {
+div.dt-scroll-body {
+ border-bottom-color: var(--bs-border-color);
+ border-bottom-width: var(--bs-border-width);
+ border-bottom-style: solid;
+}
+div.dt-scroll-body > table {
border-top: none;
margin-top: 0 !important;
margin-bottom: 0 !important;
}
-div.dataTables_scrollBody > table > thead .sorting:before,
-div.dataTables_scrollBody > table > thead .sorting_asc:before,
-div.dataTables_scrollBody > table > thead .sorting_desc:before,
-div.dataTables_scrollBody > table > thead .sorting:after,
-div.dataTables_scrollBody > table > thead .sorting_asc:after,
-div.dataTables_scrollBody > table > thead .sorting_desc:after {
- display: none;
+div.dt-scroll-body > table > tbody > tr:first-child {
+ border-top-width: 0;
}
-div.dataTables_scrollBody > table > tbody tr:first-child th,
-div.dataTables_scrollBody > table > tbody tr:first-child td {
- border-top: none;
+div.dt-scroll-body > table > thead > tr {
+ border-width: 0 !important;
+}
+div.dt-scroll-body > table > tbody > tr:last-child > * {
+ border-bottom: none;
}
-div.dataTables_scrollFoot > .dataTables_scrollFootInner {
+div.dt-scroll-foot > .dt-scroll-footInner {
box-sizing: content-box;
}
-div.dataTables_scrollFoot > .dataTables_scrollFootInner > table {
+div.dt-scroll-foot > .dt-scroll-footInner > table {
margin-top: 0 !important;
border-top: none;
}
+div.dt-scroll-foot > .dt-scroll-footInner > table > tfoot > tr:first-child {
+ border-top-width: 0 !important;
+}
@media screen and (max-width: 767px) {
- div.dataTables_wrapper div.dataTables_length,
- div.dataTables_wrapper div.dataTables_filter,
- div.dataTables_wrapper div.dataTables_info,
- div.dataTables_wrapper div.dataTables_paginate {
+ div.dt-container div.dt-length,
+ div.dt-container div.dt-search,
+ div.dt-container div.dt-info,
+ div.dt-container div.dt-paging {
text-align: center;
}
- div.dataTables_wrapper div.dataTables_paginate ul.pagination {
+ div.dt-container .row {
+ --bs-gutter-y: 0.5rem;
+ }
+ div.dt-container div.dt-paging ul.pagination {
justify-content: center !important;
}
}
-table.dataTable.table-sm > thead > tr > th:not(.sorting_disabled) {
+table.dataTable.table-sm > thead > tr th.dt-orderable-asc, table.dataTable.table-sm > thead > tr th.dt-orderable-desc, table.dataTable.table-sm > thead > tr th.dt-ordering-asc, table.dataTable.table-sm > thead > tr th.dt-ordering-desc,
+table.dataTable.table-sm > thead > tr td.dt-orderable-asc,
+table.dataTable.table-sm > thead > tr td.dt-orderable-desc,
+table.dataTable.table-sm > thead > tr td.dt-ordering-asc,
+table.dataTable.table-sm > thead > tr td.dt-ordering-desc {
padding-right: 20px;
}
-
-table.table-bordered.dataTable {
- border-right-width: 0;
-}
-table.table-bordered.dataTable thead tr:first-child th,
-table.table-bordered.dataTable thead tr:first-child td {
- border-top-width: 1px;
-}
-table.table-bordered.dataTable th,
-table.table-bordered.dataTable td {
- border-left-width: 0;
-}
-table.table-bordered.dataTable th:first-child, table.table-bordered.dataTable th:first-child,
-table.table-bordered.dataTable td:first-child,
-table.table-bordered.dataTable td:first-child {
- border-left-width: 1px;
-}
-table.table-bordered.dataTable th:last-child, table.table-bordered.dataTable th:last-child,
-table.table-bordered.dataTable td:last-child,
-table.table-bordered.dataTable td:last-child {
- border-right-width: 1px;
-}
-table.table-bordered.dataTable th,
-table.table-bordered.dataTable td {
- border-bottom-width: 1px;
+table.dataTable.table-sm > thead > tr th.dt-orderable-asc span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-orderable-desc span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-ordering-asc span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-ordering-desc span.dt-column-order,
+table.dataTable.table-sm > thead > tr td.dt-orderable-asc span.dt-column-order,
+table.dataTable.table-sm > thead > tr td.dt-orderable-desc span.dt-column-order,
+table.dataTable.table-sm > thead > tr td.dt-ordering-asc span.dt-column-order,
+table.dataTable.table-sm > thead > tr td.dt-ordering-desc span.dt-column-order {
+ right: 5px;
}
-div.dataTables_scrollHead table.table-bordered {
+div.dt-scroll-head table.table-bordered {
border-bottom-width: 0;
}
-div.table-responsive > div.dataTables_wrapper > div.row {
+div.table-responsive > div.dt-container > div.row {
margin: 0;
}
-div.table-responsive > div.dataTables_wrapper > div.row > div[class^=col-]:first-child {
+div.table-responsive > div.dt-container > div.row > div[class^=col-]:first-child {
padding-left: 0;
}
-div.table-responsive > div.dataTables_wrapper > div.row > div[class^=col-]:last-child {
+div.table-responsive > div.dt-container > div.row > div[class^=col-]:last-child {
padding-right: 0;
}
diff --git a/src/static/scripts/datatables.js b/src/static/scripts/datatables.js
@@ -4,37 +4,34 @@
*
* To rebuild or modify this file with the latest versions of the included
* software please visit:
- * https://datatables.net/download/#bs5/dt-1.13.6
+ * https://datatables.net/download/#bs5/dt-2.0.8
*
* Included libraries:
- * DataTables 1.13.6
+ * DataTables 2.0.8
*/
-/*! DataTables 1.13.6
- * ©2008-2023 SpryMedia Ltd - datatables.net/license
+/*! DataTables 2.0.8
+ * © SpryMedia Ltd - datatables.net/license
*/
/**
* @summary DataTables
* @description Paginate, search and order HTML tables
- * @version 1.13.6
+ * @version 2.0.8
* @author SpryMedia Ltd
* @contact www.datatables.net
* @copyright SpryMedia Ltd.
*
* This source file is free software, available under the following license:
- * MIT license - http://datatables.net/license
+ * MIT license - https://datatables.net/license
*
* This source file 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 license files for details.
*
- * For details please refer to: http://www.datatables.net
+ * For details please refer to: https://www.datatables.net
*/
-/*jslint evil: true, undef: true, browser: true */
-/*globals $,require,jQuery,define,_selector_run,_selector_opts,_selector_first,_selector_row_indexes,_ext,_Api,_api_register,_api_registerPlural,_re_new_lines,_re_html,_re_formatted_numeric,_re_escape_regex,_empty,_intVal,_numToDecimal,_isNumber,_isHtml,_htmlNumeric,_pluck,_pluck_order,_range,_stripHtml,_unique,_fnBuildAjax,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnAjaxDataSrc,_fnAddColumn,_fnColumnOptions,_fnAdjustColumnSizing,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnVisbleColumns,_fnGetColumns,_fnColumnTypes,_fnApplyColumnDefs,_fnHungarianMap,_fnCamelToHungarian,_fnLanguageCompat,_fnBrowserDetect,_fnAddData,_fnAddTr,_fnNodeToDataIndex,_fnNodeToColumnIndex,_fnGetCellData,_fnSetCellData,_fnSplitObjNotation,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnGetDataMaster,_fnClearTable,_fnDeleteIndex,_fnInvalidate,_fnGetRowElements,_fnCreateTr,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAddOptionsHtml,_fnDetectHeader,_fnGetUniqueThs,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnFilterCreateSearch,_fnEscapeRegex,_fnFilterData,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnInfoMacros,_fnInitialise,_fnInitComplete,_fnLengthChange,_fnFeatureHtmlLength,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnFeatureHtmlTable,_fnScrollDraw,_fnApplyToChildren,_fnCalculateColumnWidths,_fnThrottle,_fnConvertToWidth,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnSortFlatten,_fnSort,_fnSortAria,_fnSortListener,_fnSortAttachListener,_fnSortingClasses,_fnSortData,_fnSaveState,_fnLoadState,_fnSettingsFromNode,_fnLog,_fnMap,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnLengthOverflow,_fnRenderer,_fnDataSource,_fnRowAttributes*/
-
(function( factory ) {
"use strict";
@@ -66,15 +63,14 @@
};
}
else {
- return factory( jq, window, window.document );
+ module.exports = factory( jq, window, window.document );
}
}
else {
// Browser
window.DataTable = factory( jQuery, window, document );
}
-}
-(function( $, window, document, undefined ) {
+}(function( $, window, document ) {
"use strict";
@@ -95,968 +91,219 @@
options = selector;
}
- /**
- * Perform a jQuery selector action on the table's TR elements (from the tbody) and
- * return the resulting jQuery object.
- * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on
- * @param {object} [oOpts] Optional parameters for modifying the rows to be included
- * @param {string} [oOpts.filter=none] Select TR elements that meet the current filter
- * criterion ("applied") or all TR elements (i.e. no filter).
- * @param {string} [oOpts.order=current] Order of the TR elements in the processed array.
- * Can be either 'current', whereby the current sorting of the table is used, or
- * 'original' whereby the original order the data was read into the table is used.
- * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page
- * ("current") or not ("all"). If 'current' is given, then order is assumed to be
- * 'current' and filter is 'applied', regardless of what they might be given as.
- * @returns {object} jQuery object, filtered by the given selector.
- * @dtopt API
- * @deprecated Since v1.10
- *
- * @example
- * $(document).ready(function() {
- * var oTable = $('#example').dataTable();
- *
- * // Highlight every second row
- * oTable.$('tr:odd').css('backgroundColor', 'blue');
- * } );
- *
- * @example
- * $(document).ready(function() {
- * var oTable = $('#example').dataTable();
- *
- * // Filter to rows with 'Webkit' in them, add a background colour and then
- * // remove the filter, thus highlighting the 'Webkit' rows only.
- * oTable.fnFilter('Webkit');
- * oTable.$('tr', {"search": "applied"}).css('backgroundColor', 'blue');
- * oTable.fnFilter('');
- * } );
- */
- this.$ = function ( sSelector, oOpts )
- {
- return this.api(true).$( sSelector, oOpts );
- };
-
-
- /**
- * Almost identical to $ in operation, but in this case returns the data for the matched
- * rows - as such, the jQuery selector used should match TR row nodes or TD/TH cell nodes
- * rather than any descendants, so the data can be obtained for the row/cell. If matching
- * rows are found, the data returned is the original data array/object that was used to
- * create the row (or a generated array if from a DOM source).
- *
- * This method is often useful in-combination with $ where both functions are given the
- * same parameters and the array indexes will match identically.
- * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on
- * @param {object} [oOpts] Optional parameters for modifying the rows to be included
- * @param {string} [oOpts.filter=none] Select elements that meet the current filter
- * criterion ("applied") or all elements (i.e. no filter).
- * @param {string} [oOpts.order=current] Order of the data in the processed array.
- * Can be either 'current', whereby the current sorting of the table is used, or
- * 'original' whereby the original order the data was read into the table is used.
- * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page
- * ("current") or not ("all"). If 'current' is given, then order is assumed to be
- * 'current' and filter is 'applied', regardless of what they might be given as.
- * @returns {array} Data for the matched elements. If any elements, as a result of the
- * selector, were not TR, TD or TH elements in the DataTable, they will have a null
- * entry in the array.
- * @dtopt API
- * @deprecated Since v1.10
- *
- * @example
- * $(document).ready(function() {
- * var oTable = $('#example').dataTable();
- *
- * // Get the data from the first row in the table
- * var data = oTable._('tr:first');
- *
- * // Do something useful with the data
- * alert( "First cell is: "+data[0] );
- * } );
- *
- * @example
- * $(document).ready(function() {
- * var oTable = $('#example').dataTable();
- *
- * // Filter to 'Webkit' and get all data for
- * oTable.fnFilter('Webkit');
- * var data = oTable._('tr', {"search": "applied"});
- *
- * // Do something with the data
- * alert( data.length+" rows matched the search" );
- * } );
- */
- this._ = function ( sSelector, oOpts )
- {
- return this.api(true).rows( sSelector, oOpts ).data();
- };
-
-
- /**
- * Create a DataTables Api instance, with the currently selected tables for
- * the Api's context.
- * @param {boolean} [traditional=false] Set the API instance's context to be
- * only the table referred to by the `DataTable.ext.iApiIndex` option, as was
- * used in the API presented by DataTables 1.9- (i.e. the traditional mode),
- * or if all tables captured in the jQuery object should be used.
- * @return {DataTables.Api}
- */
- this.api = function ( traditional )
- {
- return traditional ?
- new _Api(
- _fnSettingsFromNode( this[ _ext.iApiIndex ] )
- ) :
- new _Api( this );
- };
-
-
- /**
- * Add a single new row or multiple rows of data to the table. Please note
- * that this is suitable for client-side processing only - if you are using
- * server-side processing (i.e. "bServerSide": true), then to add data, you
- * must add it to the data source, i.e. the server-side, through an Ajax call.
- * @param {array|object} data The data to be added to the table. This can be:
- * <ul>
- * <li>1D array of data - add a single row with the data provided</li>
- * <li>2D array of arrays - add multiple rows in a single call</li>
- * <li>object - data object when using <i>mData</i></li>
- * <li>array of objects - multiple data objects when using <i>mData</i></li>
- * </ul>
- * @param {bool} [redraw=true] redraw the table or not
- * @returns {array} An array of integers, representing the list of indexes in
- * <i>aoData</i> ({@link DataTable.models.oSettings}) that have been added to
- * the table.
- * @dtopt API
- * @deprecated Since v1.10
- *
- * @example
- * // Global var for counter
- * var giCount = 2;
- *
- * $(document).ready(function() {
- * $('#example').dataTable();
- * } );
- *
- * function fnClickAddRow() {
- * $('#example').dataTable().fnAddData( [
- * giCount+".1",
- * giCount+".2",
- * giCount+".3",
- * giCount+".4" ]
- * );
- *
- * giCount++;
- * }
- */
- this.fnAddData = function( data, redraw )
- {
- var api = this.api( true );
-
- /* Check if we want to add multiple rows or not */
- var rows = Array.isArray(data) && ( Array.isArray(data[0]) || $.isPlainObject(data[0]) ) ?
- api.rows.add( data ) :
- api.row.add( data );
-
- if ( redraw === undefined || redraw ) {
- api.draw();
- }
-
- return rows.flatten().toArray();
- };
-
-
- /**
- * This function will make DataTables recalculate the column sizes, based on the data
- * contained in the table and the sizes applied to the columns (in the DOM, CSS or
- * through the sWidth parameter). This can be useful when the width of the table's
- * parent element changes (for example a window resize).
- * @param {boolean} [bRedraw=true] Redraw the table or not, you will typically want to
- * @dtopt API
- * @deprecated Since v1.10
- *
- * @example
- * $(document).ready(function() {
- * var oTable = $('#example').dataTable( {
- * "sScrollY": "200px",
- * "bPaginate": false
- * } );
- *
- * $(window).on('resize', function () {
- * oTable.fnAdjustColumnSizing();
- * } );
- * } );
- */
- this.fnAdjustColumnSizing = function ( bRedraw )
- {
- var api = this.api( true ).columns.adjust();
- var settings = api.settings()[0];
- var scroll = settings.oScroll;
-
- if ( bRedraw === undefined || bRedraw ) {
- api.draw( false );
- }
- else if ( scroll.sX !== "" || scroll.sY !== "" ) {
- /* If not redrawing, but scrolling, we want to apply the new column sizes anyway */
- _fnScrollDraw( settings );
- }
- };
-
-
- /**
- * Quickly and simply clear a table
- * @param {bool} [bRedraw=true] redraw the table or not
- * @dtopt API
- * @deprecated Since v1.10
- *
- * @example
- * $(document).ready(function() {
- * var oTable = $('#example').dataTable();
- *
- * // Immediately 'nuke' the current rows (perhaps waiting for an Ajax callback...)
- * oTable.fnClearTable();
- * } );
- */
- this.fnClearTable = function( bRedraw )
- {
- var api = this.api( true ).clear();
-
- if ( bRedraw === undefined || bRedraw ) {
- api.draw();
- }
- };
-
-
- /**
- * The exact opposite of 'opening' a row, this function will close any rows which
- * are currently 'open'.
- * @param {node} nTr the table row to 'close'
- * @returns {int} 0 on success, or 1 if failed (can't find the row)
- * @dtopt API
- * @deprecated Since v1.10
- *
- * @example
- * $(document).ready(function() {
- * var oTable;
- *
- * // 'open' an information row when a row is clicked on
- * $('#example tbody tr').click( function () {
- * if ( oTable.fnIsOpen(this) ) {
- * oTable.fnClose( this );
- * } else {
- * oTable.fnOpen( this, "Temporary row opened", "info_row" );
- * }
- * } );
- *
- * oTable = $('#example').dataTable();
- * } );
- */
- this.fnClose = function( nTr )
+ var _that = this;
+ var emptyInit = options === undefined;
+ var len = this.length;
+
+ if ( emptyInit ) {
+ options = {};
+ }
+
+ // Method to get DT API instance from jQuery object
+ this.api = function ()
{
- this.api( true ).row( nTr ).child.hide();
+ return new _Api( this );
};
-
-
- /**
- * Remove a row for the table
- * @param {mixed} target The index of the row from aoData to be deleted, or
- * the TR element you want to delete
- * @param {function|null} [callBack] Callback function
- * @param {bool} [redraw=true] Redraw the table or not
- * @returns {array} The row that was deleted
- * @dtopt API
- * @deprecated Since v1.10
- *
- * @example
- * $(document).ready(function() {
- * var oTable = $('#example').dataTable();
- *
- * // Immediately remove the first row
- * oTable.fnDeleteRow( 0 );
- * } );
- */
- this.fnDeleteRow = function( target, callback, redraw )
- {
- var api = this.api( true );
- var rows = api.rows( target );
- var settings = rows.settings()[0];
- var data = settings.aoData[ rows[0][0] ];
-
- rows.remove();
-
- if ( callback ) {
- callback.call( this, settings, data );
+
+ this.each(function() {
+ // For each initialisation we want to give it a clean initialisation
+ // object that can be bashed around
+ var o = {};
+ var oInit = len > 1 ? // optimisation for single table case
+ _fnExtend( o, options, true ) :
+ options;
+
+
+ var i=0, iLen;
+ var sId = this.getAttribute( 'id' );
+ var bInitHandedOff = false;
+ var defaults = DataTable.defaults;
+ var $this = $(this);
+
+
+ /* Sanity check */
+ if ( this.nodeName.toLowerCase() != 'table' )
+ {
+ _fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 );
+ return;
}
-
- if ( redraw === undefined || redraw ) {
- api.draw();
+
+ $(this).trigger( 'options.dt', oInit );
+
+ /* Backwards compatibility for the defaults */
+ _fnCompatOpts( defaults );
+ _fnCompatCols( defaults.column );
+
+ /* Convert the camel-case defaults to Hungarian */
+ _fnCamelToHungarian( defaults, defaults, true );
+ _fnCamelToHungarian( defaults.column, defaults.column, true );
+
+ /* Setting up the initialisation object */
+ _fnCamelToHungarian( defaults, $.extend( oInit, $this.data() ), true );
+
+
+
+ /* Check to see if we are re-initialising a table */
+ var allSettings = DataTable.settings;
+ for ( i=0, iLen=allSettings.length ; i<iLen ; i++ )
+ {
+ var s = allSettings[i];
+
+ /* Base check on table node */
+ if (
+ s.nTable == this ||
+ (s.nTHead && s.nTHead.parentNode == this) ||
+ (s.nTFoot && s.nTFoot.parentNode == this)
+ ) {
+ var bRetrieve = oInit.bRetrieve !== undefined ? oInit.bRetrieve : defaults.bRetrieve;
+ var bDestroy = oInit.bDestroy !== undefined ? oInit.bDestroy : defaults.bDestroy;
+
+ if ( emptyInit || bRetrieve )
+ {
+ return s.oInstance;
+ }
+ else if ( bDestroy )
+ {
+ new DataTable.Api(s).destroy();
+ break;
+ }
+ else
+ {
+ _fnLog( s, 0, 'Cannot reinitialise DataTable', 3 );
+ return;
+ }
+ }
+
+ /* If the element we are initialising has the same ID as a table which was previously
+ * initialised, but the table nodes don't match (from before) then we destroy the old
+ * instance by simply deleting it. This is under the assumption that the table has been
+ * destroyed by other methods. Anyone using non-id selectors will need to do this manually
+ */
+ if ( s.sTableId == this.id )
+ {
+ allSettings.splice( i, 1 );
+ break;
+ }
}
-
- return data;
- };
-
-
- /**
- * Restore the table to it's original state in the DOM by removing all of DataTables
- * enhancements, alterations to the DOM structure of the table and event listeners.
- * @param {boolean} [remove=false] Completely remove the table from the DOM
- * @dtopt API
- * @deprecated Since v1.10
- *
- * @example
- * $(document).ready(function() {
- * // This example is fairly pointless in reality, but shows how fnDestroy can be used
- * var oTable = $('#example').dataTable();
- * oTable.fnDestroy();
- * } );
- */
- this.fnDestroy = function ( remove )
- {
- this.api( true ).destroy( remove );
- };
-
-
- /**
- * Redraw the table
- * @param {bool} [complete=true] Re-filter and resort (if enabled) the table before the draw.
- * @dtopt API
- * @deprecated Since v1.10
- *
- * @example
- * $(document).ready(function() {
- * var oTable = $('#example').dataTable();
- *
- * // Re-draw the table - you wouldn't want to do it here, but it's an example :-)
- * oTable.fnDraw();
- * } );
- */
- this.fnDraw = function( complete )
- {
- // Note that this isn't an exact match to the old call to _fnDraw - it takes
- // into account the new data, but can hold position.
- this.api( true ).draw( complete );
- };
-
-
- /**
- * Filter the input based on data
- * @param {string} sInput String to filter the table on
- * @param {int|null} [iColumn] Column to limit filtering to
- * @param {bool} [bRegex=false] Treat as regular expression or not
- * @param {bool} [bSmart=true] Perform smart filtering or not
- * @param {bool} [bShowGlobal=true] Show the input global filter in it's input box(es)
- * @param {bool} [bCaseInsensitive=true] Do case-insensitive matching (true) or not (false)
- * @dtopt API
- * @deprecated Since v1.10
- *
- * @example
- * $(document).ready(function() {
- * var oTable = $('#example').dataTable();
- *
- * // Sometime later - filter...
- * oTable.fnFilter( 'test string' );
- * } );
- */
- this.fnFilter = function( sInput, iColumn, bRegex, bSmart, bShowGlobal, bCaseInsensitive )
- {
- var api = this.api( true );
-
- if ( iColumn === null || iColumn === undefined ) {
- api.search( sInput, bRegex, bSmart, bCaseInsensitive );
+
+ /* Ensure the table has an ID - required for accessibility */
+ if ( sId === null || sId === "" )
+ {
+ sId = "DataTables_Table_"+(DataTable.ext._unique++);
+ this.id = sId;
}
- else {
- api.column( iColumn ).search( sInput, bRegex, bSmart, bCaseInsensitive );
+
+ /* Create the settings object for this table and set some of the default parameters */
+ var oSettings = $.extend( true, {}, DataTable.models.oSettings, {
+ "sDestroyWidth": $this[0].style.width,
+ "sInstance": sId,
+ "sTableId": sId,
+ colgroup: $('<colgroup>').prependTo(this),
+ fastData: function (row, column, type) {
+ return _fnGetCellData(oSettings, row, column, type);
+ }
+ } );
+ oSettings.nTable = this;
+ oSettings.oInit = oInit;
+
+ allSettings.push( oSettings );
+
+ // Make a single API instance available for internal handling
+ oSettings.api = new _Api( oSettings );
+
+ // Need to add the instance after the instance after the settings object has been added
+ // to the settings array, so we can self reference the table instance if more than one
+ oSettings.oInstance = (_that.length===1) ? _that : $this.dataTable();
+
+ // Backwards compatibility, before we apply all the defaults
+ _fnCompatOpts( oInit );
+
+ // If the length menu is given, but the init display length is not, use the length menu
+ if ( oInit.aLengthMenu && ! oInit.iDisplayLength )
+ {
+ oInit.iDisplayLength = Array.isArray(oInit.aLengthMenu[0])
+ ? oInit.aLengthMenu[0][0]
+ : $.isPlainObject( oInit.aLengthMenu[0] )
+ ? oInit.aLengthMenu[0].value
+ : oInit.aLengthMenu[0];
}
-
- api.draw();
- };
-
-
- /**
- * Get the data for the whole table, an individual row or an individual cell based on the
- * provided parameters.
- * @param {int|node} [src] A TR row node, TD/TH cell node or an integer. If given as
- * a TR node then the data source for the whole row will be returned. If given as a
- * TD/TH cell node then iCol will be automatically calculated and the data for the
- * cell returned. If given as an integer, then this is treated as the aoData internal
- * data index for the row (see fnGetPosition) and the data for that row used.
- * @param {int} [col] Optional column index that you want the data of.
- * @returns {array|object|string} If mRow is undefined, then the data for all rows is
- * returned. If mRow is defined, just data for that row, and is iCol is
- * defined, only data for the designated cell is returned.
- * @dtopt API
- * @deprecated Since v1.10
- *
- * @example
- * // Row data
- * $(document).ready(function() {
- * oTable = $('#example').dataTable();
- *
- * oTable.$('tr').click( function () {
- * var data = oTable.fnGetData( this );
- * // ... do something with the array / object of data for the row
- * } );
- * } );
- *
- * @example
- * // Individual cell data
- * $(document).ready(function() {
- * oTable = $('#example').dataTable();
- *
- * oTable.$('td').click( function () {
- * var sData = oTable.fnGetData( this );
- * alert( 'The cell clicked on had the value of '+sData );
- * } );
- * } );
- */
- this.fnGetData = function( src, col )
- {
- var api = this.api( true );
-
- if ( src !== undefined ) {
- var type = src.nodeName ? src.nodeName.toLowerCase() : '';
-
- return col !== undefined || type == 'td' || type == 'th' ?
- api.cell( src, col ).data() :
- api.row( src ).data() || null;
- }
-
- return api.data().toArray();
- };
-
-
- /**
- * Get an array of the TR nodes that are used in the table's body. Note that you will
- * typically want to use the '$' API method in preference to this as it is more
- * flexible.
- * @param {int} [iRow] Optional row index for the TR element you want
- * @returns {array|node} If iRow is undefined, returns an array of all TR elements
- * in the table's body, or iRow is defined, just the TR element requested.
- * @dtopt API
- * @deprecated Since v1.10
- *
- * @example
- * $(document).ready(function() {
- * var oTable = $('#example').dataTable();
- *
- * // Get the nodes from the table
- * var nNodes = oTable.fnGetNodes( );
- * } );
- */
- this.fnGetNodes = function( iRow )
- {
- var api = this.api( true );
-
- return iRow !== undefined ?
- api.row( iRow ).node() :
- api.rows().nodes().flatten().toArray();
- };
-
-
- /**
- * Get the array indexes of a particular cell from it's DOM element
- * and column index including hidden columns
- * @param {node} node this can either be a TR, TD or TH in the table's body
- * @returns {int} If nNode is given as a TR, then a single index is returned, or
- * if given as a cell, an array of [row index, column index (visible),
- * column index (all)] is given.
- * @dtopt API
- * @deprecated Since v1.10
- *
- * @example
- * $(document).ready(function() {
- * $('#example tbody td').click( function () {
- * // Get the position of the current data from the node
- * var aPos = oTable.fnGetPosition( this );
- *
- * // Get the data array for this row
- * var aData = oTable.fnGetData( aPos[0] );
- *
- * // Update the data array and return the value
- * aData[ aPos[1] ] = 'clicked';
- * this.innerHTML = 'clicked';
- * } );
- *
- * // Init DataTables
- * oTable = $('#example').dataTable();
- * } );
- */
- this.fnGetPosition = function( node )
- {
- var api = this.api( true );
- var nodeName = node.nodeName.toUpperCase();
-
- if ( nodeName == 'TR' ) {
- return api.row( node ).index();
- }
- else if ( nodeName == 'TD' || nodeName == 'TH' ) {
- var cell = api.cell( node ).index();
-
- return [
- cell.row,
- cell.columnVisible,
- cell.column
- ];
- }
- return null;
- };
-
-
- /**
- * Check to see if a row is 'open' or not.
- * @param {node} nTr the table row to check
- * @returns {boolean} true if the row is currently open, false otherwise
- * @dtopt API
- * @deprecated Since v1.10
- *
- * @example
- * $(document).ready(function() {
- * var oTable;
- *
- * // 'open' an information row when a row is clicked on
- * $('#example tbody tr').click( function () {
- * if ( oTable.fnIsOpen(this) ) {
- * oTable.fnClose( this );
- * } else {
- * oTable.fnOpen( this, "Temporary row opened", "info_row" );
- * }
- * } );
- *
- * oTable = $('#example').dataTable();
- * } );
- */
- this.fnIsOpen = function( nTr )
- {
- return this.api( true ).row( nTr ).child.isShown();
- };
-
-
- /**
- * This function will place a new row directly after a row which is currently
- * on display on the page, with the HTML contents that is passed into the
- * function. This can be used, for example, to ask for confirmation that a
- * particular record should be deleted.
- * @param {node} nTr The table row to 'open'
- * @param {string|node|jQuery} mHtml The HTML to put into the row
- * @param {string} sClass Class to give the new TD cell
- * @returns {node} The row opened. Note that if the table row passed in as the
- * first parameter, is not found in the table, this method will silently
- * return.
- * @dtopt API
- * @deprecated Since v1.10
- *
- * @example
- * $(document).ready(function() {
- * var oTable;
- *
- * // 'open' an information row when a row is clicked on
- * $('#example tbody tr').click( function () {
- * if ( oTable.fnIsOpen(this) ) {
- * oTable.fnClose( this );
- * } else {
- * oTable.fnOpen( this, "Temporary row opened", "info_row" );
- * }
- * } );
- *
- * oTable = $('#example').dataTable();
- * } );
- */
- this.fnOpen = function( nTr, mHtml, sClass )
- {
- return this.api( true )
- .row( nTr )
- .child( mHtml, sClass )
- .show()
- .child()[0];
- };
-
-
- /**
- * Change the pagination - provides the internal logic for pagination in a simple API
- * function. With this function you can have a DataTables table go to the next,
- * previous, first or last pages.
- * @param {string|int} mAction Paging action to take: "first", "previous", "next" or "last"
- * or page number to jump to (integer), note that page 0 is the first page.
- * @param {bool} [bRedraw=true] Redraw the table or not
- * @dtopt API
- * @deprecated Since v1.10
- *
- * @example
- * $(document).ready(function() {
- * var oTable = $('#example').dataTable();
- * oTable.fnPageChange( 'next' );
- * } );
- */
- this.fnPageChange = function ( mAction, bRedraw )
- {
- var api = this.api( true ).page( mAction );
-
- if ( bRedraw === undefined || bRedraw ) {
- api.draw(false);
- }
- };
-
-
- /**
- * Show a particular column
- * @param {int} iCol The column whose display should be changed
- * @param {bool} bShow Show (true) or hide (false) the column
- * @param {bool} [bRedraw=true] Redraw the table or not
- * @dtopt API
- * @deprecated Since v1.10
- *
- * @example
- * $(document).ready(function() {
- * var oTable = $('#example').dataTable();
- *
- * // Hide the second column after initialisation
- * oTable.fnSetColumnVis( 1, false );
- * } );
- */
- this.fnSetColumnVis = function ( iCol, bShow, bRedraw )
- {
- var api = this.api( true ).column( iCol ).visible( bShow );
-
- if ( bRedraw === undefined || bRedraw ) {
- api.columns.adjust().draw();
- }
- };
-
-
- /**
- * Get the settings for a particular table for external manipulation
- * @returns {object} DataTables settings object. See
- * {@link DataTable.models.oSettings}
- * @dtopt API
- * @deprecated Since v1.10
- *
- * @example
- * $(document).ready(function() {
- * var oTable = $('#example').dataTable();
- * var oSettings = oTable.fnSettings();
- *
- * // Show an example parameter from the settings
- * alert( oSettings._iDisplayStart );
- * } );
- */
- this.fnSettings = function()
- {
- return _fnSettingsFromNode( this[_ext.iApiIndex] );
- };
-
-
- /**
- * Sort the table by a particular column
- * @param {int} iCol the data index to sort on. Note that this will not match the
- * 'display index' if you have hidden data entries
- * @dtopt API
- * @deprecated Since v1.10
- *
- * @example
- * $(document).ready(function() {
- * var oTable = $('#example').dataTable();
- *
- * // Sort immediately with columns 0 and 1
- * oTable.fnSort( [ [0,'asc'], [1,'asc'] ] );
- * } );
- */
- this.fnSort = function( aaSort )
- {
- this.api( true ).order( aaSort ).draw();
- };
-
-
- /**
- * Attach a sort listener to an element for a given column
- * @param {node} nNode the element to attach the sort listener to
- * @param {int} iColumn the column that a click on this node will sort on
- * @param {function} [fnCallback] callback function when sort is run
- * @dtopt API
- * @deprecated Since v1.10
- *
- * @example
- * $(document).ready(function() {
- * var oTable = $('#example').dataTable();
- *
- * // Sort on column 1, when 'sorter' is clicked on
- * oTable.fnSortListener( document.getElementById('sorter'), 1 );
- * } );
- */
- this.fnSortListener = function( nNode, iColumn, fnCallback )
- {
- this.api( true ).order.listener( nNode, iColumn, fnCallback );
- };
-
-
- /**
- * Update a table cell or row - this method will accept either a single value to
- * update the cell with, an array of values with one element for each column or
- * an object in the same format as the original data source. The function is
- * self-referencing in order to make the multi column updates easier.
- * @param {object|array|string} mData Data to update the cell/row with
- * @param {node|int} mRow TR element you want to update or the aoData index
- * @param {int} [iColumn] The column to update, give as null or undefined to
- * update a whole row.
- * @param {bool} [bRedraw=true] Redraw the table or not
- * @param {bool} [bAction=true] Perform pre-draw actions or not
- * @returns {int} 0 on success, 1 on error
- * @dtopt API
- * @deprecated Since v1.10
- *
- * @example
- * $(document).ready(function() {
- * var oTable = $('#example').dataTable();
- * oTable.fnUpdate( 'Example update', 0, 0 ); // Single cell
- * oTable.fnUpdate( ['a', 'b', 'c', 'd', 'e'], $('tbody tr')[0] ); // Row
- * } );
- */
- this.fnUpdate = function( mData, mRow, iColumn, bRedraw, bAction )
- {
- var api = this.api( true );
-
- if ( iColumn === undefined || iColumn === null ) {
- api.row( mRow ).data( mData );
- }
- else {
- api.cell( mRow, iColumn ).data( mData );
- }
-
- if ( bAction === undefined || bAction ) {
- api.columns.adjust();
- }
-
- if ( bRedraw === undefined || bRedraw ) {
- api.draw();
- }
- return 0;
- };
-
-
- /**
- * Provide a common method for plug-ins to check the version of DataTables being used, in order
- * to ensure compatibility.
- * @param {string} sVersion Version string to check for, in the format "X.Y.Z". Note that the
- * formats "X" and "X.Y" are also acceptable.
- * @returns {boolean} true if this version of DataTables is greater or equal to the required
- * version, or false if this version of DataTales is not suitable
- * @method
- * @dtopt API
- * @deprecated Since v1.10
- *
- * @example
- * $(document).ready(function() {
- * var oTable = $('#example').dataTable();
- * alert( oTable.fnVersionCheck( '1.9.0' ) );
- * } );
- */
- this.fnVersionCheck = _ext.fnVersionCheck;
-
-
- var _that = this;
- var emptyInit = options === undefined;
- var len = this.length;
-
- if ( emptyInit ) {
- options = {};
- }
-
- this.oApi = this.internal = _ext.internal;
-
- // Extend with old style plug-in API methods
- for ( var fn in DataTable.ext.internal ) {
- if ( fn ) {
- this[fn] = _fnExternApiFunc(fn);
- }
- }
-
- this.each(function() {
- // For each initialisation we want to give it a clean initialisation
- // object that can be bashed around
- var o = {};
- var oInit = len > 1 ? // optimisation for single table case
- _fnExtend( o, options, true ) :
- options;
-
- /*global oInit,_that,emptyInit*/
- var i=0, iLen, j, jLen, k, kLen;
- var sId = this.getAttribute( 'id' );
- var bInitHandedOff = false;
- var defaults = DataTable.defaults;
- var $this = $(this);
+ // Apply the defaults and init options to make a single init object will all
+ // options defined from defaults and instance options.
+ oInit = _fnExtend( $.extend( true, {}, defaults ), oInit );
- /* Sanity check */
- if ( this.nodeName.toLowerCase() != 'table' )
- {
- _fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 );
- return;
- }
- /* Backwards compatibility for the defaults */
- _fnCompatOpts( defaults );
- _fnCompatCols( defaults.column );
+ // Map the initialisation options onto the settings object
+ _fnMap( oSettings.oFeatures, oInit, [
+ "bPaginate",
+ "bLengthChange",
+ "bFilter",
+ "bSort",
+ "bSortMulti",
+ "bInfo",
+ "bProcessing",
+ "bAutoWidth",
+ "bSortClasses",
+ "bServerSide",
+ "bDeferRender"
+ ] );
+ _fnMap( oSettings, oInit, [
+ "ajax",
+ "fnFormatNumber",
+ "sServerMethod",
+ "aaSorting",
+ "aaSortingFixed",
+ "aLengthMenu",
+ "sPaginationType",
+ "iStateDuration",
+ "bSortCellsTop",
+ "iTabIndex",
+ "sDom",
+ "fnStateLoadCallback",
+ "fnStateSaveCallback",
+ "renderer",
+ "searchDelay",
+ "rowId",
+ "caption",
+ "layout",
+ [ "iCookieDuration", "iStateDuration" ], // backwards compat
+ [ "oSearch", "oPreviousSearch" ],
+ [ "aoSearchCols", "aoPreSearchCols" ],
+ [ "iDisplayLength", "_iDisplayLength" ]
+ ] );
+ _fnMap( oSettings.oScroll, oInit, [
+ [ "sScrollX", "sX" ],
+ [ "sScrollXInner", "sXInner" ],
+ [ "sScrollY", "sY" ],
+ [ "bScrollCollapse", "bCollapse" ]
+ ] );
+ _fnMap( oSettings.oLanguage, oInit, "fnInfoCallback" );
- /* Convert the camel-case defaults to Hungarian */
- _fnCamelToHungarian( defaults, defaults, true );
- _fnCamelToHungarian( defaults.column, defaults.column, true );
+ /* Callback functions which are array driven */
+ _fnCallbackReg( oSettings, 'aoDrawCallback', oInit.fnDrawCallback );
+ _fnCallbackReg( oSettings, 'aoStateSaveParams', oInit.fnStateSaveParams );
+ _fnCallbackReg( oSettings, 'aoStateLoadParams', oInit.fnStateLoadParams );
+ _fnCallbackReg( oSettings, 'aoStateLoaded', oInit.fnStateLoaded );
+ _fnCallbackReg( oSettings, 'aoRowCallback', oInit.fnRowCallback );
+ _fnCallbackReg( oSettings, 'aoRowCreatedCallback', oInit.fnCreatedRow );
+ _fnCallbackReg( oSettings, 'aoHeaderCallback', oInit.fnHeaderCallback );
+ _fnCallbackReg( oSettings, 'aoFooterCallback', oInit.fnFooterCallback );
+ _fnCallbackReg( oSettings, 'aoInitComplete', oInit.fnInitComplete );
+ _fnCallbackReg( oSettings, 'aoPreDrawCallback', oInit.fnPreDrawCallback );
- /* Setting up the initialisation object */
- _fnCamelToHungarian( defaults, $.extend( oInit, $this.data() ), true );
+ oSettings.rowIdFn = _fnGetObjectDataFn( oInit.rowId );
+ /* Browser support detection */
+ _fnBrowserDetect( oSettings );
+ var oClasses = oSettings.oClasses;
- /* Check to see if we are re-initialising a table */
- var allSettings = DataTable.settings;
- for ( i=0, iLen=allSettings.length ; i<iLen ; i++ )
- {
- var s = allSettings[i];
-
- /* Base check on table node */
- if (
- s.nTable == this ||
- (s.nTHead && s.nTHead.parentNode == this) ||
- (s.nTFoot && s.nTFoot.parentNode == this)
- ) {
- var bRetrieve = oInit.bRetrieve !== undefined ? oInit.bRetrieve : defaults.bRetrieve;
- var bDestroy = oInit.bDestroy !== undefined ? oInit.bDestroy : defaults.bDestroy;
-
- if ( emptyInit || bRetrieve )
- {
- return s.oInstance;
- }
- else if ( bDestroy )
- {
- s.oInstance.fnDestroy();
- break;
- }
- else
- {
- _fnLog( s, 0, 'Cannot reinitialise DataTable', 3 );
- return;
- }
- }
-
- /* If the element we are initialising has the same ID as a table which was previously
- * initialised, but the table nodes don't match (from before) then we destroy the old
- * instance by simply deleting it. This is under the assumption that the table has been
- * destroyed by other methods. Anyone using non-id selectors will need to do this manually
- */
- if ( s.sTableId == this.id )
- {
- allSettings.splice( i, 1 );
- break;
- }
- }
-
- /* Ensure the table has an ID - required for accessibility */
- if ( sId === null || sId === "" )
- {
- sId = "DataTables_Table_"+(DataTable.ext._unique++);
- this.id = sId;
- }
-
- /* Create the settings object for this table and set some of the default parameters */
- var oSettings = $.extend( true, {}, DataTable.models.oSettings, {
- "sDestroyWidth": $this[0].style.width,
- "sInstance": sId,
- "sTableId": sId
- } );
- oSettings.nTable = this;
- oSettings.oApi = _that.internal;
- oSettings.oInit = oInit;
-
- allSettings.push( oSettings );
-
- // Need to add the instance after the instance after the settings object has been added
- // to the settings array, so we can self reference the table instance if more than one
- oSettings.oInstance = (_that.length===1) ? _that : $this.dataTable();
-
- // Backwards compatibility, before we apply all the defaults
- _fnCompatOpts( oInit );
- _fnLanguageCompat( oInit.oLanguage );
-
- // If the length menu is given, but the init display length is not, use the length menu
- if ( oInit.aLengthMenu && ! oInit.iDisplayLength )
- {
- oInit.iDisplayLength = Array.isArray( oInit.aLengthMenu[0] ) ?
- oInit.aLengthMenu[0][0] : oInit.aLengthMenu[0];
- }
-
- // Apply the defaults and init options to make a single init object will all
- // options defined from defaults and instance options.
- oInit = _fnExtend( $.extend( true, {}, defaults ), oInit );
-
-
- // Map the initialisation options onto the settings object
- _fnMap( oSettings.oFeatures, oInit, [
- "bPaginate",
- "bLengthChange",
- "bFilter",
- "bSort",
- "bSortMulti",
- "bInfo",
- "bProcessing",
- "bAutoWidth",
- "bSortClasses",
- "bServerSide",
- "bDeferRender"
- ] );
- _fnMap( oSettings, oInit, [
- "asStripeClasses",
- "ajax",
- "fnServerData",
- "fnFormatNumber",
- "sServerMethod",
- "aaSorting",
- "aaSortingFixed",
- "aLengthMenu",
- "sPaginationType",
- "sAjaxSource",
- "sAjaxDataProp",
- "iStateDuration",
- "sDom",
- "bSortCellsTop",
- "iTabIndex",
- "fnStateLoadCallback",
- "fnStateSaveCallback",
- "renderer",
- "searchDelay",
- "rowId",
- [ "iCookieDuration", "iStateDuration" ], // backwards compat
- [ "oSearch", "oPreviousSearch" ],
- [ "aoSearchCols", "aoPreSearchCols" ],
- [ "iDisplayLength", "_iDisplayLength" ]
- ] );
- _fnMap( oSettings.oScroll, oInit, [
- [ "sScrollX", "sX" ],
- [ "sScrollXInner", "sXInner" ],
- [ "sScrollY", "sY" ],
- [ "bScrollCollapse", "bCollapse" ]
- ] );
- _fnMap( oSettings.oLanguage, oInit, "fnInfoCallback" );
-
- /* Callback functions which are array driven */
- _fnCallbackReg( oSettings, 'aoDrawCallback', oInit.fnDrawCallback, 'user' );
- _fnCallbackReg( oSettings, 'aoServerParams', oInit.fnServerParams, 'user' );
- _fnCallbackReg( oSettings, 'aoStateSaveParams', oInit.fnStateSaveParams, 'user' );
- _fnCallbackReg( oSettings, 'aoStateLoadParams', oInit.fnStateLoadParams, 'user' );
- _fnCallbackReg( oSettings, 'aoStateLoaded', oInit.fnStateLoaded, 'user' );
- _fnCallbackReg( oSettings, 'aoRowCallback', oInit.fnRowCallback, 'user' );
- _fnCallbackReg( oSettings, 'aoRowCreatedCallback', oInit.fnCreatedRow, 'user' );
- _fnCallbackReg( oSettings, 'aoHeaderCallback', oInit.fnHeaderCallback, 'user' );
- _fnCallbackReg( oSettings, 'aoFooterCallback', oInit.fnFooterCallback, 'user' );
- _fnCallbackReg( oSettings, 'aoInitComplete', oInit.fnInitComplete, 'user' );
- _fnCallbackReg( oSettings, 'aoPreDrawCallback', oInit.fnPreDrawCallback, 'user' );
-
- oSettings.rowIdFn = _fnGetObjectDataFn( oInit.rowId );
-
- /* Browser support detection */
- _fnBrowserDetect( oSettings );
-
- var oClasses = oSettings.oClasses;
-
- $.extend( oClasses, DataTable.ext.classes, oInit.oClasses );
- $this.addClass( oClasses.sTable );
+ $.extend( oClasses, DataTable.ext.classes, oInit.oClasses );
+ $this.addClass( oClasses.table );
+ if (! oSettings.oFeatures.bPaginate) {
+ oInit.iDisplayStart = 0;
+ }
if ( oSettings.iInitDisplayStart === undefined )
{
@@ -1065,14 +312,6 @@
oSettings._iDisplayStart = oInit.iDisplayStart;
}
- if ( oInit.iDeferLoading !== null )
- {
- oSettings.bDeferLoading = true;
- var tmp = Array.isArray( oInit.iDeferLoading );
- oSettings._iRecordsDisplay = tmp ? oInit.iDeferLoading[0] : oInit.iDeferLoading;
- oSettings._iRecordsTotal = tmp ? oInit.iDeferLoading[1] : oInit.iDeferLoading;
- }
-
/* Language definitions */
var oLanguage = oSettings.oLanguage;
$.extend( true, oLanguage, oInit.oLanguage );
@@ -1088,14 +327,16 @@
url: oLanguage.sUrl,
success: function ( json ) {
_fnCamelToHungarian( defaults.oLanguage, json );
- _fnLanguageCompat( json );
$.extend( true, oLanguage, json, oSettings.oInit.oLanguage );
- _fnCallbackFire( oSettings, null, 'i18n', [oSettings]);
+ _fnCallbackFire( oSettings, null, 'i18n', [oSettings], true);
_fnInitialise( oSettings );
},
error: function () {
- // Error occurred loading language file, continue on as best we can
+ // Error occurred loading language file
+ _fnLog( oSettings, 0, 'i18n file loading error', 21 );
+
+ // continue on as best we can
_fnInitialise( oSettings );
}
} );
@@ -1106,67 +347,38 @@
}
/*
- * Stripes
- */
- if ( oInit.asStripeClasses === null )
- {
- oSettings.asStripeClasses =[
- oClasses.sStripeOdd,
- oClasses.sStripeEven
- ];
- }
-
- /* Remove row stripe classes if they are already on the table row */
- var stripeClasses = oSettings.asStripeClasses;
- var rowOne = $this.children('tbody').find('tr').eq(0);
- if ( $.inArray( true, $.map( stripeClasses, function(el, i) {
- return rowOne.hasClass(el);
- } ) ) !== -1 ) {
- $('tbody tr', this).removeClass( stripeClasses.join(' ') );
- oSettings.asDestroyStripes = stripeClasses.slice();
- }
-
- /*
* Columns
* See if we should load columns automatically or use defined ones
*/
- var anThs = [];
- var aoColumnsInit;
- var nThead = this.getElementsByTagName('thead');
- if ( nThead.length !== 0 )
- {
- _fnDetectHeader( oSettings.aoHeader, nThead[0] );
- anThs = _fnGetUniqueThs( oSettings );
- }
+ var columnsInit = [];
+ var thead = this.getElementsByTagName('thead');
+ var initHeaderLayout = _fnDetectHeader( oSettings, thead[0] );
- /* If not given a column array, generate one with nulls */
- if ( oInit.aoColumns === null )
- {
- aoColumnsInit = [];
- for ( i=0, iLen=anThs.length ; i<iLen ; i++ )
- {
- aoColumnsInit.push( null );
- }
+ // If we don't have a columns array, then generate one with nulls
+ if ( oInit.aoColumns ) {
+ columnsInit = oInit.aoColumns;
}
- else
- {
- aoColumnsInit = oInit.aoColumns;
+ else if ( initHeaderLayout.length ) {
+ for ( i=0, iLen=initHeaderLayout[0].length ; i<iLen ; i++ ) {
+ columnsInit.push( null );
+ }
}
- /* Add the columns */
- for ( i=0, iLen=aoColumnsInit.length ; i<iLen ; i++ )
- {
- _fnAddColumn( oSettings, anThs ? anThs[i] : null );
+ // Add the columns
+ for ( i=0, iLen=columnsInit.length ; i<iLen ; i++ ) {
+ _fnAddColumn( oSettings );
}
- /* Apply the column definitions */
- _fnApplyColumnDefs( oSettings, oInit.aoColumnDefs, aoColumnsInit, function (iCol, oDef) {
+ // Apply the column definitions
+ _fnApplyColumnDefs( oSettings, oInit.aoColumnDefs, columnsInit, initHeaderLayout, function (iCol, oDef) {
_fnColumnOptions( oSettings, iCol, oDef );
} );
/* HTML5 attribute detection - build an mData object automatically if the
* attributes are found
*/
+ var rowOne = $this.children('tbody').find('tr').eq(0);
+
if ( rowOne.length ) {
var a = function ( cell, name ) {
return cell.getAttribute( 'data-'+name ) !== null ? name : null;
@@ -1219,44 +431,39 @@
*/
_fnSortingClasses( oSettings );
- if ( features.bSort ) {
- _fnCallbackReg( oSettings, 'aoDrawCallback', function () {
- if ( oSettings.bSorted ) {
- var aSort = _fnSortFlatten( oSettings );
- var sortedColumns = {};
-
- $.each( aSort, function (i, val) {
- sortedColumns[ val.src ] = val.dir;
- } );
-
- _fnCallbackFire( oSettings, null, 'order', [oSettings, aSort, sortedColumns] );
- _fnSortAria( oSettings );
- }
- } );
- }
-
_fnCallbackReg( oSettings, 'aoDrawCallback', function () {
if ( oSettings.bSorted || _fnDataSource( oSettings ) === 'ssp' || features.bDeferRender ) {
_fnSortingClasses( oSettings );
}
- }, 'sc' );
+ } );
/*
* Final init
* Cache the header, body and footer as required, creating them if needed
*/
+ var caption = $this.children('caption');
- // Work around for Webkit bug 83867 - store the caption-side before removing from doc
- var captions = $this.children('caption').each( function () {
- this._captionSide = $(this).css('caption-side');
- } );
+ if ( oSettings.caption ) {
+ if ( caption.length === 0 ) {
+ caption = $('<caption/>').appendTo( $this );
+ }
+
+ caption.html( oSettings.caption );
+ }
+
+ // Store the caption side, so we can remove the element from the document
+ // when creating the element
+ if (caption.length) {
+ caption[0]._captionSide = caption.css('caption-side');
+ oSettings.captionNode = caption[0];
+ }
- var thead = $this.children('thead');
if ( thead.length === 0 ) {
thead = $('<thead/>').appendTo($this);
}
oSettings.nTHead = thead[0];
+ $('tr', thead).addClass(oClasses.thead.row);
var tbody = $this.children('tbody');
if ( tbody.length === 0 ) {
@@ -1265,31 +472,22 @@
oSettings.nTBody = tbody[0];
var tfoot = $this.children('tfoot');
- if ( tfoot.length === 0 && captions.length > 0 && (oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "") ) {
+ if ( tfoot.length === 0 ) {
// If we are a scrolling table, and no footer has been given, then we need to create
// a tfoot element for the caption element to be appended to
tfoot = $('<tfoot/>').appendTo($this);
}
+ oSettings.nTFoot = tfoot[0];
+ $('tr', tfoot).addClass(oClasses.tfoot.row);
- if ( tfoot.length === 0 || tfoot.children().length === 0 ) {
- $this.addClass( oClasses.sNoFooter );
- }
- else if ( tfoot.length > 0 ) {
- oSettings.nTFoot = tfoot[0];
- _fnDetectHeader( oSettings.aoFooter, oSettings.nTFoot );
- }
-
- /* Check if there is data passing into the constructor */
+ // Check if there is data passing into the constructor
if ( oInit.aaData ) {
for ( i=0 ; i<oInit.aaData.length ; i++ ) {
_fnAddData( oSettings, oInit.aaData[ i ] );
}
}
- else if ( oSettings.bDeferLoading || _fnDataSource( oSettings ) == 'dom' ) {
- /* Grab the data from the page - only do this when deferred loading or no Ajax
- * source since there is no point in reading the DOM data if we are then going
- * to replace it with Ajax data
- */
+ else if ( _fnDataSource( oSettings ) == 'dom' ) {
+ // Grab the data from the page
_fnAddTr( oSettings, $(oSettings.nTBody).children('tr') );
}
@@ -1308,7 +506,7 @@
};
/* Must be done after everything which can be overridden by the state saving! */
- _fnCallbackReg( oSettings, 'aoDrawCallback', _fnSaveState, 'state_save' );
+ _fnCallbackReg( oSettings, 'aoDrawCallback', _fnSaveState );
if ( oInit.bStateSave )
{
@@ -1325,6 +523,569 @@
};
+
+ /**
+ * DataTables extensions
+ *
+ * This namespace acts as a collection area for plug-ins that can be used to
+ * extend DataTables capabilities. Indeed many of the build in methods
+ * use this method to provide their own capabilities (sorting methods for
+ * example).
+ *
+ * Note that this namespace is aliased to `jQuery.fn.dataTableExt` for legacy
+ * reasons
+ *
+ * @namespace
+ */
+ DataTable.ext = _ext = {
+ /**
+ * Buttons. For use with the Buttons extension for DataTables. This is
+ * defined here so other extensions can define buttons regardless of load
+ * order. It is _not_ used by DataTables core.
+ *
+ * @type object
+ * @default {}
+ */
+ buttons: {},
+
+
+ /**
+ * Element class names
+ *
+ * @type object
+ * @default {}
+ */
+ classes: {},
+
+
+ /**
+ * DataTables build type (expanded by the download builder)
+ *
+ * @type string
+ */
+ builder: "bs5/dt-2.0.8",
+
+
+ /**
+ * Error reporting.
+ *
+ * How should DataTables report an error. Can take the value 'alert',
+ * 'throw', 'none' or a function.
+ *
+ * @type string|function
+ * @default alert
+ */
+ errMode: "alert",
+
+
+ /**
+ * Legacy so v1 plug-ins don't throw js errors on load
+ */
+ feature: [],
+
+ /**
+ * Feature plug-ins.
+ *
+ * This is an object of callbacks which provide the features for DataTables
+ * to be initialised via the `layout` option.
+ */
+ features: {},
+
+
+ /**
+ * Row searching.
+ *
+ * This method of searching is complimentary to the default type based
+ * searching, and a lot more comprehensive as it allows you complete control
+ * over the searching logic. Each element in this array is a function
+ * (parameters described below) that is called for every row in the table,
+ * and your logic decides if it should be included in the searching data set
+ * or not.
+ *
+ * Searching functions have the following input parameters:
+ *
+ * 1. `{object}` DataTables settings object: see
+ * {@link DataTable.models.oSettings}
+ * 2. `{array|object}` Data for the row to be processed (same as the
+ * original format that was passed in as the data source, or an array
+ * from a DOM data source
+ * 3. `{int}` Row index ({@link DataTable.models.oSettings.aoData}), which
+ * can be useful to retrieve the `TR` element if you need DOM interaction.
+ *
+ * And the following return is expected:
+ *
+ * * {boolean} Include the row in the searched result set (true) or not
+ * (false)
+ *
+ * Note that as with the main search ability in DataTables, technically this
+ * is "filtering", since it is subtractive. However, for consistency in
+ * naming we call it searching here.
+ *
+ * @type array
+ * @default []
+ *
+ * @example
+ * // The following example shows custom search being applied to the
+ * // fourth column (i.e. the data[3] index) based on two input values
+ * // from the end-user, matching the data in a certain range.
+ * $.fn.dataTable.ext.search.push(
+ * function( settings, data, dataIndex ) {
+ * var min = document.getElementById('min').value * 1;
+ * var max = document.getElementById('max').value * 1;
+ * var version = data[3] == "-" ? 0 : data[3]*1;
+ *
+ * if ( min == "" && max == "" ) {
+ * return true;
+ * }
+ * else if ( min == "" && version < max ) {
+ * return true;
+ * }
+ * else if ( min < version && "" == max ) {
+ * return true;
+ * }
+ * else if ( min < version && version < max ) {
+ * return true;
+ * }
+ * return false;
+ * }
+ * );
+ */
+ search: [],
+
+
+ /**
+ * Selector extensions
+ *
+ * The `selector` option can be used to extend the options available for the
+ * selector modifier options (`selector-modifier` object data type) that
+ * each of the three built in selector types offer (row, column and cell +
+ * their plural counterparts). For example the Select extension uses this
+ * mechanism to provide an option to select only rows, columns and cells
+ * that have been marked as selected by the end user (`{selected: true}`),
+ * which can be used in conjunction with the existing built in selector
+ * options.
+ *
+ * Each property is an array to which functions can be pushed. The functions
+ * take three attributes:
+ *
+ * * Settings object for the host table
+ * * Options object (`selector-modifier` object type)
+ * * Array of selected item indexes
+ *
+ * The return is an array of the resulting item indexes after the custom
+ * selector has been applied.
+ *
+ * @type object
+ */
+ selector: {
+ cell: [],
+ column: [],
+ row: []
+ },
+
+
+ /**
+ * Legacy configuration options. Enable and disable legacy options that
+ * are available in DataTables.
+ *
+ * @type object
+ */
+ legacy: {
+ /**
+ * Enable / disable DataTables 1.9 compatible server-side processing
+ * requests
+ *
+ * @type boolean
+ * @default null
+ */
+ ajax: null
+ },
+
+
+ /**
+ * Pagination plug-in methods.
+ *
+ * Each entry in this object is a function and defines which buttons should
+ * be shown by the pagination rendering method that is used for the table:
+ * {@link DataTable.ext.renderer.pageButton}. The renderer addresses how the
+ * buttons are displayed in the document, while the functions here tell it
+ * what buttons to display. This is done by returning an array of button
+ * descriptions (what each button will do).
+ *
+ * Pagination types (the four built in options and any additional plug-in
+ * options defined here) can be used through the `paginationType`
+ * initialisation parameter.
+ *
+ * The functions defined take two parameters:
+ *
+ * 1. `{int} page` The current page index
+ * 2. `{int} pages` The number of pages in the table
+ *
+ * Each function is expected to return an array where each element of the
+ * array can be one of:
+ *
+ * * `first` - Jump to first page when activated
+ * * `last` - Jump to last page when activated
+ * * `previous` - Show previous page when activated
+ * * `next` - Show next page when activated
+ * * `{int}` - Show page of the index given
+ * * `{array}` - A nested array containing the above elements to add a
+ * containing 'DIV' element (might be useful for styling).
+ *
+ * Note that DataTables v1.9- used this object slightly differently whereby
+ * an object with two functions would be defined for each plug-in. That
+ * ability is still supported by DataTables 1.10+ to provide backwards
+ * compatibility, but this option of use is now decremented and no longer
+ * documented in DataTables 1.10+.
+ *
+ * @type object
+ * @default {}
+ *
+ * @example
+ * // Show previous, next and current page buttons only
+ * $.fn.dataTableExt.oPagination.current = function ( page, pages ) {
+ * return [ 'previous', page, 'next' ];
+ * };
+ */
+ pager: {},
+
+
+ renderer: {
+ pageButton: {},
+ header: {}
+ },
+
+
+ /**
+ * Ordering plug-ins - custom data source
+ *
+ * The extension options for ordering of data available here is complimentary
+ * to the default type based ordering that DataTables typically uses. It
+ * allows much greater control over the the data that is being used to
+ * order a column, but is necessarily therefore more complex.
+ *
+ * This type of ordering is useful if you want to do ordering based on data
+ * live from the DOM (for example the contents of an 'input' element) rather
+ * than just the static string that DataTables knows of.
+ *
+ * The way these plug-ins work is that you create an array of the values you
+ * wish to be ordering for the column in question and then return that
+ * array. The data in the array much be in the index order of the rows in
+ * the table (not the currently ordering order!). Which order data gathering
+ * function is run here depends on the `dt-init columns.orderDataType`
+ * parameter that is used for the column (if any).
+ *
+ * The functions defined take two parameters:
+ *
+ * 1. `{object}` DataTables settings object: see
+ * {@link DataTable.models.oSettings}
+ * 2. `{int}` Target column index
+ *
+ * Each function is expected to return an array:
+ *
+ * * `{array}` Data for the column to be ordering upon
+ *
+ * @type array
+ *
+ * @example
+ * // Ordering using `input` node values
+ * $.fn.dataTable.ext.order['dom-text'] = function ( settings, col )
+ * {
+ * return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) {
+ * return $('input', td).val();
+ * } );
+ * }
+ */
+ order: {},
+
+
+ /**
+ * Type based plug-ins.
+ *
+ * Each column in DataTables has a type assigned to it, either by automatic
+ * detection or by direct assignment using the `type` option for the column.
+ * The type of a column will effect how it is ordering and search (plug-ins
+ * can also make use of the column type if required).
+ *
+ * @namespace
+ */
+ type: {
+ /**
+ * Automatic column class assignment
+ */
+ className: {},
+
+ /**
+ * Type detection functions.
+ *
+ * The functions defined in this object are used to automatically detect
+ * a column's type, making initialisation of DataTables super easy, even
+ * when complex data is in the table.
+ *
+ * The functions defined take two parameters:
+ *
+ * 1. `{*}` Data from the column cell to be analysed
+ * 2. `{settings}` DataTables settings object. This can be used to
+ * perform context specific type detection - for example detection
+ * based on language settings such as using a comma for a decimal
+ * place. Generally speaking the options from the settings will not
+ * be required
+ *
+ * Each function is expected to return:
+ *
+ * * `{string|null}` Data type detected, or null if unknown (and thus
+ * pass it on to the other type detection functions.
+ *
+ * @type array
+ *
+ * @example
+ * // Currency type detection plug-in:
+ * $.fn.dataTable.ext.type.detect.push(
+ * function ( data, settings ) {
+ * // Check the numeric part
+ * if ( ! data.substring(1).match(/[0-9]/) ) {
+ * return null;
+ * }
+ *
+ * // Check prefixed by currency
+ * if ( data.charAt(0) == '$' || data.charAt(0) == '£' ) {
+ * return 'currency';
+ * }
+ * return null;
+ * }
+ * );
+ */
+ detect: [],
+
+ /**
+ * Automatic renderer assignment
+ */
+ render: {},
+
+
+ /**
+ * Type based search formatting.
+ *
+ * The type based searching functions can be used to pre-format the
+ * data to be search on. For example, it can be used to strip HTML
+ * tags or to de-format telephone numbers for numeric only searching.
+ *
+ * Note that is a search is not defined for a column of a given type,
+ * no search formatting will be performed.
+ *
+ * Pre-processing of searching data plug-ins - When you assign the sType
+ * for a column (or have it automatically detected for you by DataTables
+ * or a type detection plug-in), you will typically be using this for
+ * custom sorting, but it can also be used to provide custom searching
+ * by allowing you to pre-processing the data and returning the data in
+ * the format that should be searched upon. This is done by adding
+ * functions this object with a parameter name which matches the sType
+ * for that target column. This is the corollary of <i>afnSortData</i>
+ * for searching data.
+ *
+ * The functions defined take a single parameter:
+ *
+ * 1. `{*}` Data from the column cell to be prepared for searching
+ *
+ * Each function is expected to return:
+ *
+ * * `{string|null}` Formatted string that will be used for the searching.
+ *
+ * @type object
+ * @default {}
+ *
+ * @example
+ * $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) {
+ * return d.replace(/\n/g," ").replace( /<.*?>/g, "" );
+ * }
+ */
+ search: {},
+
+
+ /**
+ * Type based ordering.
+ *
+ * The column type tells DataTables what ordering to apply to the table
+ * when a column is sorted upon. The order for each type that is defined,
+ * is defined by the functions available in this object.
+ *
+ * Each ordering option can be described by three properties added to
+ * this object:
+ *
+ * * `{type}-pre` - Pre-formatting function
+ * * `{type}-asc` - Ascending order function
+ * * `{type}-desc` - Descending order function
+ *
+ * All three can be used together, only `{type}-pre` or only
+ * `{type}-asc` and `{type}-desc` together. It is generally recommended
+ * that only `{type}-pre` is used, as this provides the optimal
+ * implementation in terms of speed, although the others are provided
+ * for compatibility with existing Javascript sort functions.
+ *
+ * `{type}-pre`: Functions defined take a single parameter:
+ *
+ * 1. `{*}` Data from the column cell to be prepared for ordering
+ *
+ * And return:
+ *
+ * * `{*}` Data to be sorted upon
+ *
+ * `{type}-asc` and `{type}-desc`: Functions are typical Javascript sort
+ * functions, taking two parameters:
+ *
+ * 1. `{*}` Data to compare to the second parameter
+ * 2. `{*}` Data to compare to the first parameter
+ *
+ * And returning:
+ *
+ * * `{*}` Ordering match: <0 if first parameter should be sorted lower
+ * than the second parameter, ===0 if the two parameters are equal and
+ * >0 if the first parameter should be sorted height than the second
+ * parameter.
+ *
+ * @type object
+ * @default {}
+ *
+ * @example
+ * // Numeric ordering of formatted numbers with a pre-formatter
+ * $.extend( $.fn.dataTable.ext.type.order, {
+ * "string-pre": function(x) {
+ * a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" );
+ * return parseFloat( a );
+ * }
+ * } );
+ *
+ * @example
+ * // Case-sensitive string ordering, with no pre-formatting method
+ * $.extend( $.fn.dataTable.ext.order, {
+ * "string-case-asc": function(x,y) {
+ * return ((x < y) ? -1 : ((x > y) ? 1 : 0));
+ * },
+ * "string-case-desc": function(x,y) {
+ * return ((x < y) ? 1 : ((x > y) ? -1 : 0));
+ * }
+ * } );
+ */
+ order: {}
+ },
+
+ /**
+ * Unique DataTables instance counter
+ *
+ * @type int
+ * @private
+ */
+ _unique: 0,
+
+
+ //
+ // Depreciated
+ // The following properties are retained for backwards compatibility only.
+ // The should not be used in new projects and will be removed in a future
+ // version
+ //
+
+ /**
+ * Version check function.
+ * @type function
+ * @depreciated Since 1.10
+ */
+ fnVersionCheck: DataTable.fnVersionCheck,
+
+
+ /**
+ * Index for what 'this' index API functions should use
+ * @type int
+ * @deprecated Since v1.10
+ */
+ iApiIndex: 0,
+
+
+ /**
+ * Software version
+ * @type string
+ * @deprecated Since v1.10
+ */
+ sVersion: DataTable.version
+ };
+
+
+ //
+ // Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts
+ //
+ $.extend( _ext, {
+ afnFiltering: _ext.search,
+ aTypes: _ext.type.detect,
+ ofnSearch: _ext.type.search,
+ oSort: _ext.type.order,
+ afnSortData: _ext.order,
+ aoFeatures: _ext.feature,
+ oStdClasses: _ext.classes,
+ oPagination: _ext.pager
+ } );
+
+
+ $.extend( DataTable.ext.classes, {
+ container: 'dt-container',
+ empty: {
+ row: 'dt-empty'
+ },
+ info: {
+ container: 'dt-info'
+ },
+ length: {
+ container: 'dt-length',
+ select: 'dt-input'
+ },
+ order: {
+ canAsc: 'dt-orderable-asc',
+ canDesc: 'dt-orderable-desc',
+ isAsc: 'dt-ordering-asc',
+ isDesc: 'dt-ordering-desc',
+ none: 'dt-orderable-none',
+ position: 'sorting_'
+ },
+ processing: {
+ container: 'dt-processing'
+ },
+ scrolling: {
+ body: 'dt-scroll-body',
+ container: 'dt-scroll',
+ footer: {
+ self: 'dt-scroll-foot',
+ inner: 'dt-scroll-footInner'
+ },
+ header: {
+ self: 'dt-scroll-head',
+ inner: 'dt-scroll-headInner'
+ }
+ },
+ search: {
+ container: 'dt-search',
+ input: 'dt-input'
+ },
+ table: 'dataTable',
+ tbody: {
+ cell: '',
+ row: ''
+ },
+ thead: {
+ cell: '',
+ row: ''
+ },
+ tfoot: {
+ cell: '',
+ row: ''
+ },
+ paging: {
+ active: 'current',
+ button: 'dt-paging-button',
+ container: 'dt-paging',
+ disabled: 'disabled'
+ }
+ } );
+
+
/*
* It is useful to have variables which are scoped locally so only the
* DataTables functions can access them and they don't leak into global space.
@@ -1338,7 +1099,6 @@
// Defined else where
// _selector_run
// _selector_opts
- // _selector_first
// _selector_row_indexes
var _ext; // DataTable.ext
@@ -1348,16 +1108,17 @@
var _re_dic = {};
var _re_new_lines = /[\r\n\u2028]/g;
- var _re_html = /<.*?>/g;
+ var _re_html = /<([^>]*>)/g;
+ var _max_str_len = Math.pow(2, 28);
// This is not strict ISO8601 - Date.parse() is quite lax, although
// implementations differ between browsers.
- var _re_date = /^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/;
+ var _re_date = /^\d{2,4}[./-]\d{1,2}[./-]\d{1,2}([T ]{1}\d{1,2}[:.]\d{2}([.:]\d{2})?)?$/;
// Escape regular expression special characters
var _re_escape_regex = new RegExp( '(\\' + [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ].join('|\\') + ')', 'g' );
- // http://en.wikipedia.org/wiki/Foreign_exchange_market
+ // https://en.wikipedia.org/wiki/Foreign_exchange_market
// - \u20BD - Russian ruble.
// - \u20a9 - South Korean Won
// - \u20BA - Turkish Lira
@@ -1427,12 +1188,17 @@
return _empty( d ) || typeof d === 'string';
};
-
+ // Is a string a number surrounded by HTML?
var _htmlNumeric = function ( d, decimalPoint, formatted ) {
if ( _empty( d ) ) {
return true;
}
+ // input and select strings mean that this isn't just a number
+ if (typeof d === 'string' && d.match(/<(input|select)/i)) {
+ return null;
+ }
+
var html = _isHtml( d );
return ! html ?
null :
@@ -1485,7 +1251,9 @@
}
else {
for ( ; i<ien ; i++ ) {
- out.push( a[ order[i] ][ prop ] );
+ if ( a[ order[i] ] ) {
+ out.push( a[ order[i] ][ prop ] );
+ }
}
}
@@ -1528,13 +1296,58 @@
return out;
};
+ // Replaceable function in api.util
+ var _stripHtml = function (input) {
+ // Irrelevant check to workaround CodeQL's false positive on the regex
+ if (input.length > _max_str_len) {
+ throw new Error('Exceeded max str len');
+ }
+
+ var previous;
+
+ input = input.replace(_re_html, ''); // Complete tags
+
+ // Safety for incomplete script tag - use do / while to ensure that
+ // we get all instances
+ do {
+ previous = input;
+ input = input.replace(/<script/i, '');
+ } while (input !== previous);
+
+ return previous;
+ };
+
+ // Replaceable function in api.util
+ var _escapeHtml = function ( d ) {
+ if (Array.isArray(d)) {
+ d = d.join(',');
+ }
- var _stripHtml = function ( d ) {
- return d
- .replace( _re_html, '' ) // Complete tags
- .replace(/<script/i, ''); // Safety for incomplete script tag
+ return typeof d === 'string' ?
+ d
+ .replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"') :
+ d;
};
+ // Remove diacritics from a string by decomposing it and then removing
+ // non-ascii characters
+ var _normalize = function (str, both) {
+ if (typeof str !== 'string') {
+ return str;
+ }
+
+ // It is faster to just run `normalize` than it is to check if
+ // we need to with a regex!
+ var res = str.normalize("NFD");
+
+ // Equally, here we check if a regex is needed or not
+ return res.length !== str.length
+ ? (both === true ? str + ' ' : '' ) + res.replace(/[\u0300-\u036f]/g, "")
+ : res;
+ }
/**
* Determine if all values in the array are unique. This means we can short
@@ -1574,13 +1387,17 @@
*/
var _unique = function ( src )
{
+ if (Array.from && Set) {
+ return Array.from(new Set(src));
+ }
+
if ( _areAllUnique( src ) ) {
return src.slice();
}
// A faster unique method is to use object keys to identify used values,
// but this doesn't work with arrays or objects, which we must also
- // consider. See jsperf.com/compare-array-unique-versions/4 for more
+ // consider. See jsperf.app/compare-array-unique-versions/4 for more
// information.
var
out = [],
@@ -1615,40 +1432,20 @@
else {
out.push(val);
}
-
+
return out;
}
- var _includes = function (search, start) {
- if (start === undefined) {
- start = 0;
+ // Similar to jQuery's addClass, but use classList.add
+ function _addClass(el, name) {
+ if (name) {
+ name.split(' ').forEach(function (n) {
+ if (n) {
+ // `add` does deduplication, so no need to check `contains`
+ el.classList.add(n);
+ }
+ });
}
-
- return this.indexOf(search, start) !== -1;
- };
-
- // Array.isArray polyfill.
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray
- if (! Array.isArray) {
- Array.isArray = function(arg) {
- return Object.prototype.toString.call(arg) === '[object Array]';
- };
- }
-
- if (! Array.prototype.includes) {
- Array.prototype.includes = _includes;
- }
-
- // .trim() polyfill
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim
- if (!String.prototype.trim) {
- String.prototype.trim = function () {
- return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
- };
- }
-
- if (! String.prototype.includes) {
- String.prototype.includes = _includes;
}
/**
@@ -1663,6 +1460,43 @@
*/
DataTable.util = {
/**
+ * Return a string with diacritic characters decomposed
+ * @param {*} mixed Function or string to normalize
+ * @param {*} both Return original string and the normalized string
+ * @returns String or undefined
+ */
+ diacritics: function (mixed, both) {
+ var type = typeof mixed;
+
+ if (type !== 'function') {
+ return _normalize(mixed, both);
+ }
+ _normalize = mixed;
+ },
+
+ /**
+ * Debounce a function
+ *
+ * @param {function} fn Function to be called
+ * @param {integer} freq Call frequency in mS
+ * @return {function} Wrapped function
+ */
+ debounce: function ( fn, timeout ) {
+ var timer;
+
+ return function () {
+ var that = this;
+ var args = arguments;
+
+ clearTimeout(timer);
+
+ timer = setTimeout( function () {
+ fn.apply(that, args);
+ }, timeout || 250 );
+ };
+ },
+
+ /**
* Throttle the calls to a function. Arguments and context are maintained
* for the throttled function.
*
@@ -1697,7 +1531,6 @@
};
},
-
/**
* Escape a string such that it can be used in a regular expression
*
@@ -1731,9 +1564,10 @@
source( data, 'set', val, meta );
};
}
- else if ( typeof source === 'string' && (source.indexOf('.') !== -1 ||
- source.indexOf('[') !== -1 || source.indexOf('(') !== -1) )
- {
+ else if (
+ typeof source === 'string' && (source.indexOf('.') !== -1 ||
+ source.indexOf('[') !== -1 || source.indexOf('(') !== -1)
+ ) {
// Like the get, we need to get data from a nested object
var setData = function (data, val, src) {
var a = _fnSplitObjNotation( src ), b;
@@ -1849,9 +1683,10 @@
return source( data, type, row, meta );
};
}
- else if ( typeof source === 'string' && (source.indexOf('.') !== -1 ||
- source.indexOf('[') !== -1 || source.indexOf('(') !== -1) )
- {
+ else if (
+ typeof source === 'string' && (source.indexOf('.') !== -1 ||
+ source.indexOf('[') !== -1 || source.indexOf('(') !== -1)
+ ) {
/* If there is a . in the source string then the data source is in a
* nested object so we loop over the data for each level to get the next
* level down. On each loop we test for undefined, and if found immediately
@@ -1926,11 +1761,39 @@
}
else {
// Array or flat object mapping
- return function (data, type) { // row and meta also passed, but not used
+ return function (data) { // row and meta also passed, but not used
return data[source];
};
}
- }
+ },
+
+ stripHtml: function (mixed) {
+ var type = typeof mixed;
+
+ if (type === 'function') {
+ _stripHtml = mixed;
+ return;
+ }
+ else if (type === 'string') {
+ return _stripHtml(mixed);
+ }
+ return mixed;
+ },
+
+ escapeHtml: function (mixed) {
+ var type = typeof mixed;
+
+ if (type === 'function') {
+ _escapeHtml = mixed;
+ return;
+ }
+ else if (type === 'string' || Array.isArray(mixed)) {
+ return _escapeHtml(mixed);
+ }
+ return mixed;
+ },
+
+ unique: _unique
};
@@ -1950,7 +1813,7 @@
newKey,
map = {};
- $.each( o, function (key, val) {
+ $.each( o, function (key) {
match = key.match(/^([^A-Z]+?)([A-Z])/);
if ( match && hungarian.indexOf(match[1]+' ') !== -1 )
@@ -1988,7 +1851,7 @@
var hungarianKey;
- $.each( user, function (key, val) {
+ $.each( user, function (key) {
hungarianKey = src._hungarianMap[ key ];
if ( hungarianKey !== undefined && (force || user[hungarianKey] === undefined) )
@@ -2011,57 +1874,6 @@
} );
}
-
- /**
- * Language compatibility - when certain options are given, and others aren't, we
- * need to duplicate the values over, in order to provide backwards compatibility
- * with older language files.
- * @param {object} oSettings dataTables settings object
- * @memberof DataTable#oApi
- */
- function _fnLanguageCompat( lang )
- {
- // Note the use of the Hungarian notation for the parameters in this method as
- // this is called after the mapping of camelCase to Hungarian
- var defaults = DataTable.defaults.oLanguage;
-
- // Default mapping
- var defaultDecimal = defaults.sDecimal;
- if ( defaultDecimal ) {
- _addNumericSort( defaultDecimal );
- }
-
- if ( lang ) {
- var zeroRecords = lang.sZeroRecords;
-
- // Backwards compatibility - if there is no sEmptyTable given, then use the same as
- // sZeroRecords - assuming that is given.
- if ( ! lang.sEmptyTable && zeroRecords &&
- defaults.sEmptyTable === "No data available in table" )
- {
- _fnMap( lang, lang, 'sZeroRecords', 'sEmptyTable' );
- }
-
- // Likewise with loading records
- if ( ! lang.sLoadingRecords && zeroRecords &&
- defaults.sLoadingRecords === "Loading..." )
- {
- _fnMap( lang, lang, 'sZeroRecords', 'sLoadingRecords' );
- }
-
- // Old parameter name of the thousands separator mapped onto the new
- if ( lang.sInfoThousands ) {
- lang.sThousands = lang.sInfoThousands;
- }
-
- var decimal = lang.sDecimal;
- if ( decimal && defaultDecimal !== decimal ) {
- _addNumericSort( decimal );
- }
- }
- }
-
-
/**
* Map one parameter onto another
* @param {object} o Object to map
@@ -2113,6 +1925,11 @@
}
}
}
+
+ // Enable search delay if server-side processing is enabled
+ if (init.serverSide && ! init.searchDelay) {
+ init.searchDelay = 400;
+ }
}
@@ -2156,7 +1973,7 @@
.css( {
position: 'fixed',
top: 0,
- left: $(window).scrollLeft()*-1, // allow for scrolling
+ left: -1 * window.pageXOffset, // allow for scrolling
height: 1,
width: 1,
overflow: 'hidden'
@@ -2183,31 +2000,13 @@
var outer = n.children();
var inner = outer.children();
- // Numbers below, in order, are:
- // inner.offsetWidth, inner.clientWidth, outer.offsetWidth, outer.clientWidth
- //
- // IE6 XP: 100 100 100 83
- // IE7 Vista: 100 100 100 83
- // IE 8+ Windows: 83 83 100 83
- // Evergreen Windows: 83 83 100 83
- // Evergreen Mac with scrollbars: 85 85 100 85
- // Evergreen Mac without scrollbars: 100 100 100 100
-
// Get scrollbar width
browser.barWidth = outer[0].offsetWidth - outer[0].clientWidth;
- // IE6/7 will oversize a width 100% element inside a scrolling element, to
- // include the width of the scrollbar, while other browsers ensure the inner
- // element is contained without forcing scrolling
- browser.bScrollOversize = inner[0].offsetWidth === 100 && outer[0].clientWidth !== 100;
-
// In rtl text layout, some browsers (most, but not all) will place the
// scrollbar on the left, rather than the right.
browser.bScrollbarLeft = Math.round( inner.offset().left ) !== 1;
- // IE8- don't provide height and width for getBoundingClientRect
- browser.bBounding = n[0].getBoundingClientRect().width ? true : false;
-
n.remove();
}
@@ -2215,58 +2014,22 @@
settings.oScroll.iBarWidth = DataTable.__browser.barWidth;
}
-
- /**
- * Array.prototype reduce[Right] method, used for browsers which don't support
- * JS 1.6. Done this way to reduce code size, since we iterate either way
- * @param {object} settings dataTables settings object
- * @memberof DataTable#oApi
- */
- function _fnReduce ( that, fn, init, start, end, inc )
- {
- var
- i = start,
- value,
- isSet = false;
-
- if ( init !== undefined ) {
- value = init;
- isSet = true;
- }
-
- while ( i !== end ) {
- if ( ! that.hasOwnProperty(i) ) {
- continue;
- }
-
- value = isSet ?
- fn( value, that[i], i, that ) :
- that[i];
-
- isSet = true;
- i += inc;
- }
-
- return value;
- }
-
/**
* Add a column to the list used for the table with default values
* @param {object} oSettings dataTables settings object
- * @param {node} nTh The th element for this column
* @memberof DataTable#oApi
*/
- function _fnAddColumn( oSettings, nTh )
+ function _fnAddColumn( oSettings )
{
// Add column to aoColumns array
var oDefaults = DataTable.defaults.column;
var iCol = oSettings.aoColumns.length;
var oCol = $.extend( {}, DataTable.models.oColumn, oDefaults, {
- "nTh": nTh ? nTh : document.createElement('th'),
- "sTitle": oDefaults.sTitle ? oDefaults.sTitle : nTh ? nTh.innerHTML : '',
"aDataSort": oDefaults.aDataSort ? oDefaults.aDataSort : [iCol],
"mData": oDefaults.mData ? oDefaults.mData : iCol,
- idx: iCol
+ idx: iCol,
+ searchFixed: {},
+ colEl: $('<col>').attr('data-dt-column', iCol)
} );
oSettings.aoColumns.push( oCol );
@@ -2275,9 +2038,6 @@
// with only some of the parameters defined, and also not give a default
var searchCols = oSettings.aoPreSearchCols;
searchCols[ iCol ] = $.extend( {}, DataTable.models.oSearch, searchCols[ iCol ] );
-
- // Use the default column options function to initialise classes etc
- _fnColumnOptions( oSettings, iCol, $(nTh).data() );
}
@@ -2291,21 +2051,6 @@
function _fnColumnOptions( oSettings, iCol, oOptions )
{
var oCol = oSettings.aoColumns[ iCol ];
- var oClasses = oSettings.oClasses;
- var th = $(oCol.nTh);
-
- // Try to get width information from the DOM. We can't get it from CSS
- // as we'd need to parse the CSS stylesheet. `width` option can override
- if ( ! oCol.sWidthOrig ) {
- // Width attribute
- oCol.sWidthOrig = th.attr('width') || null;
-
- // Style attribute
- var t = (th.attr('style') || '').match(/width:\s*(\d+[pxem%]+)/);
- if ( t ) {
- oCol.sWidthOrig = t[1];
- }
- }
/* User specified column options */
if ( oOptions !== undefined && oOptions !== null )
@@ -2326,16 +2071,13 @@
{
oCol._sManualType = oOptions.sType;
}
-
+
// `class` is a reserved word in Javascript, so we need to provide
// the ability to use a valid name for the camel case input
if ( oOptions.className && ! oOptions.sClass )
{
oOptions.sClass = oOptions.className;
}
- if ( oOptions.sClass ) {
- th.addClass( oOptions.sClass );
- }
var origClass = oCol.sClass;
@@ -2355,19 +2097,23 @@
{
oCol.aDataSort = [ oOptions.iDataSort ];
}
- _fnMap( oCol, oOptions, "aDataSort" );
-
- // Fall back to the aria-label attribute on the table header if no ariaTitle is
- // provided.
- if (! oCol.ariaTitle) {
- oCol.ariaTitle = th.attr("aria-label");
- }
+ _fnMap( oCol, oOptions, "aDataSort" );
}
/* Cache the data get and set functions for speed */
var mDataSrc = oCol.mData;
var mData = _fnGetObjectDataFn( mDataSrc );
- var mRender = oCol.mRender ? _fnGetObjectDataFn( oCol.mRender ) : null;
+
+ // The `render` option can be given as an array to access the helper rendering methods.
+ // The first element is the rendering method to use, the rest are the parameters to pass
+ if ( oCol.mRender && Array.isArray( oCol.mRender ) ) {
+ var copy = oCol.mRender.slice();
+ var name = copy.shift();
+
+ oCol.mRender = DataTable.render[name].apply(window, copy);
+ }
+
+ oCol._render = oCol.mRender ? _fnGetObjectDataFn( oCol.mRender ) : null;
var attrTest = function( src ) {
return typeof src === 'string' && src.indexOf('@') !== -1;
@@ -2380,8 +2126,8 @@
oCol.fnGetData = function (rowData, type, meta) {
var innerData = mData( rowData, type, undefined, meta );
- return mRender && type ?
- mRender( innerData, type, rowData, meta ) :
+ return oCol._render && type ?
+ oCol._render( innerData, type, rowData, meta ) :
innerData;
};
oCol.fnSetData = function ( rowData, val, meta ) {
@@ -2398,31 +2144,6 @@
if ( !oSettings.oFeatures.bSort )
{
oCol.bSortable = false;
- th.addClass( oClasses.sSortableNone ); // Have to add class here as order event isn't called
- }
-
- /* Check that the class assignment is correct for sorting */
- var bAsc = $.inArray('asc', oCol.asSorting) !== -1;
- var bDesc = $.inArray('desc', oCol.asSorting) !== -1;
- if ( !oCol.bSortable || (!bAsc && !bDesc) )
- {
- oCol.sSortingClass = oClasses.sSortableNone;
- oCol.sSortingClassJUI = "";
- }
- else if ( bAsc && !bDesc )
- {
- oCol.sSortingClass = oClasses.sSortableAsc;
- oCol.sSortingClassJUI = oClasses.sSortJUIAscAllowed;
- }
- else if ( !bAsc && bDesc )
- {
- oCol.sSortingClass = oClasses.sSortableDesc;
- oCol.sSortingClassJUI = oClasses.sSortJUIDescAllowed;
- }
- else
- {
- oCol.sSortingClass = oClasses.sSortable;
- oCol.sSortingClassJUI = oClasses.sSortJUI;
}
}
@@ -2435,27 +2156,33 @@
*/
function _fnAdjustColumnSizing ( settings )
{
- /* Not interested in doing column width calculation if auto-width is disabled */
- if ( settings.oFeatures.bAutoWidth !== false )
- {
- var columns = settings.aoColumns;
-
- _fnCalculateColumnWidths( settings );
- for ( var i=0 , iLen=columns.length ; i<iLen ; i++ )
- {
- columns[i].nTh.style.width = columns[i].sWidth;
- }
- }
+ _fnCalculateColumnWidths( settings );
+ _fnColumnSizes( settings );
var scroll = settings.oScroll;
- if ( scroll.sY !== '' || scroll.sX !== '')
- {
+ if ( scroll.sY !== '' || scroll.sX !== '') {
_fnScrollDraw( settings );
}
_fnCallbackFire( settings, null, 'column-sizing', [settings] );
}
+ /**
+ * Apply column sizes
+ *
+ * @param {*} settings DataTables settings object
+ */
+ function _fnColumnSizes ( settings )
+ {
+ var cols = settings.aoColumns;
+
+ for (var i=0 ; i<cols.length ; i++) {
+ var width = _fnColumnsSumWidth(settings, [i], false, false);
+
+ cols[i].colEl.css('width', width);
+ }
+ }
+
/**
* Convert the index of a visible column to the index in the data array (take account
@@ -2486,7 +2213,7 @@
function _fnColumnIndexToVisible( oSettings, iMatch )
{
var aiVis = _fnGetColumns( oSettings, 'bVisible' );
- var iPos = $.inArray( iMatch, aiVis );
+ var iPos = aiVis.indexOf(iMatch);
return iPos !== -1 ? iPos : null;
}
@@ -2498,16 +2225,19 @@
* @returns {int} i the number of visible columns
* @memberof DataTable#oApi
*/
- function _fnVisbleColumns( oSettings )
+ function _fnVisbleColumns( settings )
{
+ var layout = settings.aoHeader;
+ var columns = settings.aoColumns;
var vis = 0;
- // No reduce in IE8, use a loop for now
- $.each( oSettings.aoColumns, function ( i, col ) {
- if ( col.bVisible && $(col.nTh).css('display') !== 'none' ) {
- vis++;
+ if ( layout.length ) {
+ for ( var i=0, ien=layout[0].length ; i<ien ; i++ ) {
+ if ( columns[i].bVisible && $(layout[0][i].cell).css('display') !== 'none' ) {
+ vis++;
+ }
}
- } );
+ }
return vis;
}
@@ -2525,7 +2255,7 @@
{
var a = [];
- $.map( oSettings.aoColumns, function(val, i) {
+ oSettings.aoColumns.map( function(val, i) {
if ( val[sParam] ) {
a.push( i );
}
@@ -2546,7 +2276,7 @@
var data = settings.aoData;
var types = DataTable.ext.type.detect;
var i, ien, j, jen, k, ken;
- var col, cell, detectedType, cache;
+ var col, detectedType, cache;
// For each column, spin over the
for ( i=0, ien=columns.length ; i<ien ; i++ ) {
@@ -2559,6 +2289,11 @@
else if ( ! col.sType ) {
for ( j=0, jen=types.length ; j<jen ; j++ ) {
for ( k=0, ken=data.length ; k<ken ; k++ ) {
+
+ if (! data[k]) {
+ continue;
+ }
+
// Use a cache array so we only need to get the type data
// from the formatter once (when using multiple detectors)
if ( cache[k] === undefined ) {
@@ -2572,7 +2307,7 @@
// exception for the last type which is `html`. We need to
// scan all rows since it is possible to mix string and HTML
// types
- if ( ! detectedType && j !== types.length-1 ) {
+ if ( ! detectedType && j !== types.length-2 ) {
break;
}
@@ -2597,9 +2332,61 @@
col.sType = 'string';
}
}
+
+ // Set class names for header / footer for auto type classes
+ var autoClass = _ext.type.className[col.sType];
+
+ if (autoClass) {
+ _columnAutoClass(settings.aoHeader, i, autoClass);
+ _columnAutoClass(settings.aoFooter, i, autoClass);
+ }
+
+ var renderer = _ext.type.render[col.sType];
+
+ // This can only happen once! There is no way to remover
+ // a renderer. After the first time the renderer has
+ // already been set so createTr will run the renderer itself.
+ if (renderer && ! col._render) {
+ col._render = DataTable.util.get(renderer);
+
+ _columnAutoRender(settings, i);
+ }
+ }
+ }
+
+ /**
+ * Apply an auto detected renderer to data which doesn't yet have
+ * a renderer
+ */
+ function _columnAutoRender(settings, colIdx) {
+ var data = settings.aoData;
+
+ for (var i=0 ; i<data.length ; i++) {
+ if (data[i].nTr) {
+ // We have to update the display here since there is no
+ // invalidation check for the data
+ var display = _fnGetCellData( settings, i, colIdx, 'display' );
+
+ data[i].displayData[colIdx] = display;
+ _fnWriteCell(data[i].anCells[colIdx], display);
+
+ // No need to update sort / filter data since it has
+ // been invalidated and will be re-read with the
+ // renderer now applied
+ }
}
}
+ /**
+ * Apply a class name to a column's header cells
+ */
+ function _columnAutoClass(container, colIdx, className) {
+ container.forEach(function (row) {
+ if (row[colIdx] && row[colIdx].unique) {
+ _addClass(row[colIdx].cell, className);
+ }
+ });
+ }
/**
* Take the column definitions and static columns arrays and calculate how
@@ -2608,15 +2395,24 @@
* @param {object} oSettings dataTables settings object
* @param {array} aoColDefs The aoColumnDefs array that is to be applied
* @param {array} aoCols The aoColumns array that defines columns individually
+ * @param {array} headerLayout Layout for header as it was loaded
* @param {function} fn Callback function - takes two parameters, the calculated
* column index and the definition for that column.
* @memberof DataTable#oApi
*/
- function _fnApplyColumnDefs( oSettings, aoColDefs, aoCols, fn )
+ function _fnApplyColumnDefs( oSettings, aoColDefs, aoCols, headerLayout, fn )
{
var i, iLen, j, jLen, k, kLen, def;
var columns = oSettings.aoColumns;
+ if ( aoCols ) {
+ for ( i=0, iLen=aoCols.length ; i<iLen ; i++ ) {
+ if (aoCols[i] && aoCols[i].name) {
+ columns[i].sName = aoCols[i].name;
+ }
+ }
+ }
+
// Column definitions with aTargets
if ( aoColDefs )
{
@@ -2639,32 +2435,56 @@
for ( j=0, jLen=aTargets.length ; j<jLen ; j++ )
{
- if ( typeof aTargets[j] === 'number' && aTargets[j] >= 0 )
+ var target = aTargets[j];
+
+ if ( typeof target === 'number' && target >= 0 )
{
/* Add columns that we don't yet know about */
- while( columns.length <= aTargets[j] )
+ while( columns.length <= target )
{
_fnAddColumn( oSettings );
}
/* Integer, basic index */
- fn( aTargets[j], def );
+ fn( target, def );
}
- else if ( typeof aTargets[j] === 'number' && aTargets[j] < 0 )
+ else if ( typeof target === 'number' && target < 0 )
{
/* Negative integer, right to left column counting */
- fn( columns.length+aTargets[j], def );
+ fn( columns.length+target, def );
}
- else if ( typeof aTargets[j] === 'string' )
+ else if ( typeof target === 'string' )
{
- /* Class name matching on TH element */
- for ( k=0, kLen=columns.length ; k<kLen ; k++ )
- {
- if ( aTargets[j] == "_all" ||
- $(columns[k].nTh).hasClass( aTargets[j] ) )
- {
+ for ( k=0, kLen=columns.length ; k<kLen ; k++ ) {
+ if (target === '_all') {
+ // Apply to all columns
fn( k, def );
}
+ else if (target.indexOf(':name') !== -1) {
+ // Column selector
+ if (columns[k].sName === target.replace(':name', '')) {
+ fn( k, def );
+ }
+ }
+ else {
+ // Cell selector
+ headerLayout.forEach(function (row) {
+ if (row[k]) {
+ var cell = $(row[k].cell);
+
+ // Legacy support. Note that it means that we don't support
+ // an element name selector only, since they are treated as
+ // class names for 1.x compat.
+ if (target.match(/^[a-z][\w-]*$/i)) {
+ target = '.' + target;
+ }
+
+ if (cell.is( target )) {
+ fn( k, def );
+ }
+ }
+ });
+ }
}
}
}
@@ -2672,65 +2492,124 @@
}
// Statically defined columns array
- if ( aoCols )
- {
- for ( i=0, iLen=aoCols.length ; i<iLen ; i++ )
- {
+ if ( aoCols ) {
+ for ( i=0, iLen=aoCols.length ; i<iLen ; i++ ) {
fn( i, aoCols[i] );
}
}
}
+
+ /**
+ * Get the width for a given set of columns
+ *
+ * @param {*} settings DataTables settings object
+ * @param {*} targets Columns - comma separated string or array of numbers
+ * @param {*} original Use the original width (true) or calculated (false)
+ * @param {*} incVisible Include visible columns (true) or not (false)
+ * @returns Combined CSS value
+ */
+ function _fnColumnsSumWidth( settings, targets, original, incVisible ) {
+ if ( ! Array.isArray( targets ) ) {
+ targets = _fnColumnsFromHeader( targets );
+ }
+
+ var sum = 0;
+ var unit;
+ var columns = settings.aoColumns;
+
+ for ( var i=0, ien=targets.length ; i<ien ; i++ ) {
+ var column = columns[ targets[i] ];
+ var definedWidth = original ?
+ column.sWidthOrig :
+ column.sWidth;
+
+ if ( ! incVisible && column.bVisible === false ) {
+ continue;
+ }
+
+ if ( definedWidth === null || definedWidth === undefined ) {
+ return null; // can't determine a defined width - browser defined
+ }
+ else if ( typeof definedWidth === 'number' ) {
+ unit = 'px';
+ sum += definedWidth;
+ }
+ else {
+ var matched = definedWidth.match(/([\d\.]+)([^\d]*)/);
+
+ if ( matched ) {
+ sum += matched[1] * 1;
+ unit = matched.length === 3 ?
+ matched[2] :
+ 'px';
+ }
+ }
+ }
+
+ return sum + unit;
+ }
+
+ function _fnColumnsFromHeader( cell )
+ {
+ var attr = $(cell).closest('[data-dt-column]').attr('data-dt-column');
+
+ if ( ! attr ) {
+ return [];
+ }
+
+ return attr.split(',').map( function (val) {
+ return val * 1;
+ } );
+ }
/**
* Add a data array to the table, creating DOM node etc. This is the parallel to
* _fnGatherData, but for adding rows from a Javascript source, rather than a
* DOM source.
- * @param {object} oSettings dataTables settings object
- * @param {array} aData data array to be added
- * @param {node} [nTr] TR element to add to the table - optional. If not given,
+ * @param {object} settings dataTables settings object
+ * @param {array} data data array to be added
+ * @param {node} [tr] TR element to add to the table - optional. If not given,
* DataTables will create a row automatically
- * @param {array} [anTds] Array of TD|TH elements for the row - must be given
+ * @param {array} [tds] Array of TD|TH elements for the row - must be given
* if nTr is.
* @returns {int} >=0 if successful (index of new aoData entry), -1 if failed
* @memberof DataTable#oApi
*/
- function _fnAddData ( oSettings, aDataIn, nTr, anTds )
+ function _fnAddData ( settings, dataIn, tr, tds )
{
/* Create the object for storing information about this new row */
- var iRow = oSettings.aoData.length;
- var oData = $.extend( true, {}, DataTable.models.oRow, {
- src: nTr ? 'dom' : 'data',
- idx: iRow
+ var rowIdx = settings.aoData.length;
+ var rowModel = $.extend( true, {}, DataTable.models.oRow, {
+ src: tr ? 'dom' : 'data',
+ idx: rowIdx
} );
- oData._aData = aDataIn;
- oSettings.aoData.push( oData );
+ rowModel._aData = dataIn;
+ settings.aoData.push( rowModel );
- /* Create the cells */
- var nTd, sThisType;
- var columns = oSettings.aoColumns;
+ var columns = settings.aoColumns;
- // Invalidate the column types as the new data needs to be revalidated
for ( var i=0, iLen=columns.length ; i<iLen ; i++ )
{
+ // Invalidate the column types as the new data needs to be revalidated
columns[i].sType = null;
}
/* Add to the display array */
- oSettings.aiDisplayMaster.push( iRow );
+ settings.aiDisplayMaster.push( rowIdx );
- var id = oSettings.rowIdFn( aDataIn );
+ var id = settings.rowIdFn( dataIn );
if ( id !== undefined ) {
- oSettings.aIds[ id ] = oData;
+ settings.aIds[ id ] = rowModel;
}
/* Create the DOM information, or register it if already present */
- if ( nTr || ! oSettings.oFeatures.bDeferRender )
+ if ( tr || ! settings.oFeatures.bDeferRender )
{
- _fnCreateTr( oSettings, iRow, nTr, anTds );
+ _fnCreateTr( settings, rowIdx, tr, tds );
}
- return iRow;
+ return rowIdx;
}
@@ -2761,33 +2640,6 @@
/**
- * Take a TR element and convert it to an index in aoData
- * @param {object} oSettings dataTables settings object
- * @param {node} n the TR element to find
- * @returns {int} index if the node is found, null if not
- * @memberof DataTable#oApi
- */
- function _fnNodeToDataIndex( oSettings, n )
- {
- return (n._DT_RowIndex!==undefined) ? n._DT_RowIndex : null;
- }
-
-
- /**
- * Take a TD element and convert it into a column data index (not the visible index)
- * @param {object} oSettings dataTables settings object
- * @param {int} iRow The row number the TD/TH can be found in
- * @param {node} n The TD/TH element to find
- * @returns {int} index if the node is found, -1 if not
- * @memberof DataTable#oApi
- */
- function _fnNodeToColumnIndex( oSettings, iRow, n )
- {
- return $.inArray( n, oSettings.aoData[ iRow ].anCells );
- }
-
-
- /**
* Get the data for a given cell from the internal cache, taking into account data mapping
* @param {object} settings dataTables settings object
* @param {int} rowIdx aoData row id
@@ -2805,9 +2657,15 @@
type = 'sort';
}
+ var row = settings.aoData[rowIdx];
+
+ if (! row) {
+ return undefined;
+ }
+
var draw = settings.iDraw;
var col = settings.aoColumns[colIdx];
- var rowData = settings.aoData[rowIdx]._aData;
+ var rowData = row._aData;
var defaultContent = col.sDefaultContent;
var cellData = col.fnGetData( rowData, type, {
settings: settings,
@@ -2815,6 +2673,11 @@
col: colIdx
} );
+ // Allow for a node being returned for non-display types
+ if (type !== 'display' && cellData && typeof cellData === 'object' && cellData.nodeName) {
+ cellData = cellData.innerHTML;
+ }
+
if ( cellData === undefined ) {
if ( settings.iDrawError != draw && defaultContent === null ) {
_fnLog( settings, 0, "Requested unknown parameter "+
@@ -2872,6 +2735,23 @@
} );
}
+ /**
+ * Write a value to a cell
+ * @param {*} td Cell
+ * @param {*} val Value
+ */
+ function _fnWriteCell(td, val)
+ {
+ if (val && typeof val === 'object' && val.nodeName) {
+ $(td)
+ .empty()
+ .append(val);
+ }
+ else {
+ td.innerHTML = val;
+ }
+ }
+
// Private variable that is used to match action syntax in the data property object
var __reArray = /\[.*?\]$/;
@@ -2884,7 +2764,9 @@
*/
function _fnSplitObjNotation( str )
{
- return $.map( str.match(/(\\.|[^\.])+/g) || [''], function ( s ) {
+ var parts = str.match(/(\\.|[^.])+/g) || [''];
+
+ return parts.map( function ( s ) {
return s.replace(/\\\./g, '.');
} );
}
@@ -2936,36 +2818,6 @@
}
- /**
- * Take an array of integers (index array) and remove a target integer (value - not
- * the key!)
- * @param {array} a Index array to target
- * @param {int} iTarget value to find
- * @memberof DataTable#oApi
- */
- function _fnDeleteIndex( a, iTarget, splice )
- {
- var iTargetIndex = -1;
-
- for ( var i=0, iLen=a.length ; i<iLen ; i++ )
- {
- if ( a[i] == iTarget )
- {
- iTargetIndex = i;
- }
- else if ( a[i] > iTarget )
- {
- a[i]--;
- }
- }
-
- if ( iTargetIndex != -1 && splice === undefined )
- {
- a.splice( iTargetIndex, 1 );
- }
- }
-
-
/**
* Mark cached data as invalid such that a re-read of the data will occur when
* the cached data is next requested. Also update from the data source object.
@@ -2986,16 +2838,11 @@
{
var row = settings.aoData[ rowIdx ];
var i, ien;
- var cellWrite = function ( cell, col ) {
- // This is very frustrating, but in IE if you just write directly
- // to innerHTML, and elements that are overwritten are GC'ed,
- // even if there is a reference to them elsewhere
- while ( cell.childNodes.length ) {
- cell.removeChild( cell.firstChild );
- }
- cell.innerHTML = _fnGetCellData( settings, rowIdx, col, 'display' );
- };
+ // Remove the cached data for the row
+ row._aSortData = null;
+ row._aFilterData = null;
+ row.displayData = null;
// Are we reading last data from DOM or the data object?
if ( src === 'dom' || ((! src || src === 'auto') && row.src === 'dom') ) {
@@ -3008,33 +2855,34 @@
else {
// Reading from data object, update the DOM
var cells = row.anCells;
+ var display = _fnGetRowDisplay(settings, rowIdx);
if ( cells ) {
if ( colIdx !== undefined ) {
- cellWrite( cells[colIdx], colIdx );
+ _fnWriteCell(cells[colIdx], display[colIdx]);
}
else {
for ( i=0, ien=cells.length ; i<ien ; i++ ) {
- cellWrite( cells[i], i );
+ _fnWriteCell(cells[i], display[i]);
}
}
}
}
- // For both row and cell invalidation, the cached data for sorting and
- // filtering is nulled out
- row._aSortData = null;
- row._aFilterData = null;
-
- // Invalidate the type for a specific column (if given) or all columns since
- // the data might have changed
+ // Column specific invalidation
var cols = settings.aoColumns;
if ( colIdx !== undefined ) {
+ // Type - the data might have changed
cols[ colIdx ].sType = null;
+
+ // Max length string. Its a fairly cheep recalculation, so not worth
+ // something more complicated
+ cols[ colIdx ].maxLenString = null;
}
else {
for ( i=0, ien=cols.length ; i<ien ; i++ ) {
cols[i].sType = null;
+ cols[i].maxLenString = null;
}
// Update DataTables special `DT_*` attributes for the row
@@ -3065,7 +2913,7 @@
var
tds = [],
td = row.firstChild,
- name, col, o, i=0, contents,
+ name, col, i=0, contents,
columns = settings.aoColumns,
objectRead = settings._rowReadObject;
@@ -3159,6 +3007,29 @@
cells: tds
};
}
+
+ /**
+ * Render and cache a row's display data for the columns, if required
+ * @returns
+ */
+ function _fnGetRowDisplay (settings, rowIdx) {
+ let rowModal = settings.aoData[rowIdx];
+ let columns = settings.aoColumns;
+
+ if (! rowModal.displayData) {
+ // Need to render and cache
+ rowModal.displayData = [];
+
+ for ( var colIdx=0, len=columns.length ; colIdx<len ; colIdx++ ) {
+ rowModal.displayData.push(
+ _fnGetCellData( settings, rowIdx, colIdx, 'display' )
+ );
+ }
+ }
+
+ return rowModal.displayData;
+ }
+
/**
* Create a new TR element (and it's TD children) for a row
* @param {object} oSettings dataTables settings object
@@ -3176,7 +3047,8 @@
rowData = row._aData,
cells = [],
nTr, nTd, oCol,
- i, iLen, create;
+ i, iLen, create,
+ trClass = oSettings.oClasses.tbody.row;
if ( row.nTr === null )
{
@@ -3185,6 +3057,8 @@
row.nTr = nTr;
row.anCells = cells;
+ _addClass(nTr, trClass);
+
/* Use a private property on the node to allow reserve mapping from the node
* to the aoData array for fast look up
*/
@@ -3197,7 +3071,7 @@
for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
{
oCol = oSettings.aoColumns[i];
- create = nTrIn ? false : true;
+ create = nTrIn && anTds[i] ? false : true;
nTd = create ? document.createElement( oCol.sCellType ) : anTds[i];
@@ -3211,26 +3085,26 @@
};
cells.push( nTd );
+
+ var display = _fnGetRowDisplay(oSettings, iRow);
// Need to create the HTML if new, or if a rendering function is defined
- if ( create || ((oCol.mRender || oCol.mData !== i) &&
- (!$.isPlainObject(oCol.mData) || oCol.mData._ !== i+'.display')
- )) {
- nTd.innerHTML = _fnGetCellData( oSettings, iRow, i, 'display' );
- }
-
- /* Add user defined class */
- if ( oCol.sClass )
- {
- nTd.className += ' '+oCol.sClass;
+ if (
+ create ||
+ (
+ (oCol.mRender || oCol.mData !== i) &&
+ (!$.isPlainObject(oCol.mData) || oCol.mData._ !== i+'.display')
+ )
+ ) {
+ _fnWriteCell(nTd, display[i]);
}
// Visibility - add or remove as required
- if ( oCol.bVisible && ! nTrIn )
+ if ( oCol.bVisible && create )
{
nTr.appendChild( nTd );
}
- else if ( ! oCol.bVisible && nTrIn )
+ else if ( ! oCol.bVisible && ! create )
{
nTd.parentNode.removeChild( nTd );
}
@@ -3243,7 +3117,10 @@
}
}
- _fnCallbackFire( oSettings, 'aoRowCreatedCallback', null, [nTr, rowData, iRow, cells] );
+ _fnCallbackFire( oSettings, 'aoRowCreatedCallback', 'row-created', [nTr, rowData, iRow, cells] );
+ }
+ else {
+ _addClass(row.nTr, trClass);
}
}
@@ -3295,182 +3172,184 @@
* @param {object} oSettings dataTables settings object
* @memberof DataTable#oApi
*/
- function _fnBuildHead( oSettings )
+ function _fnBuildHead( settings, side )
{
- var i, ien, cell, row, column;
- var thead = oSettings.nTHead;
- var tfoot = oSettings.nTFoot;
- var createHeader = $('th, td', thead).length === 0;
- var classes = oSettings.oClasses;
- var columns = oSettings.aoColumns;
-
- if ( createHeader ) {
- row = $('<tr/>').appendTo( thead );
+ var classes = settings.oClasses;
+ var columns = settings.aoColumns;
+ var i, ien, row;
+ var target = side === 'header'
+ ? settings.nTHead
+ : settings.nTFoot;
+ var titleProp = side === 'header' ? 'sTitle' : side;
+
+ // Footer might be defined
+ if (! target) {
+ return;
}
- for ( i=0, ien=columns.length ; i<ien ; i++ ) {
- column = columns[i];
- cell = $( column.nTh ).addClass( column.sClass );
+ // If no cells yet and we have content for them, then create
+ if (side === 'header' || _pluck(settings.aoColumns, titleProp).join('')) {
+ row = $('tr', target);
- if ( createHeader ) {
- cell.appendTo( row );
+ // Add a row if needed
+ if (! row.length) {
+ row = $('<tr/>').appendTo(target)
}
- // 1.11 move into sorting
- if ( oSettings.oFeatures.bSort ) {
- cell.addClass( column.sSortingClass );
-
- if ( column.bSortable !== false ) {
- cell
- .attr( 'tabindex', oSettings.iTabIndex )
- .attr( 'aria-controls', oSettings.sTableId );
+ // Add the number of cells needed to make up to the number of columns
+ if (row.length === 1) {
+ var cells = $('td, th', row);
- _fnSortAttachListener( oSettings, column.nTh, i );
+ for ( i=cells.length, ien=columns.length ; i<ien ; i++ ) {
+ $('<th/>')
+ .html( columns[i][titleProp] || '' )
+ .appendTo( row );
}
}
+ }
- if ( column.sTitle != cell[0].innerHTML ) {
- cell.html( column.sTitle );
- }
+ var detected = _fnDetectHeader( settings, target, true );
- _fnRenderer( oSettings, 'header' )(
- oSettings, cell, column, classes
- );
+ if (side === 'header') {
+ settings.aoHeader = detected;
}
-
- if ( createHeader ) {
- _fnDetectHeader( oSettings.aoHeader, thead );
+ else {
+ settings.aoFooter = detected;
}
- /* Deal with the footer - add classes if required */
- $(thead).children('tr').children('th, td').addClass( classes.sHeaderTH );
- $(tfoot).children('tr').children('th, td').addClass( classes.sFooterTH );
+ // ARIA role for the rows
+ $(target).children('tr').attr('role', 'row');
- // Cache the footer cells. Note that we only take the cells from the first
- // row in the footer. If there is more than one row the user wants to
- // interact with, they need to use the table().foot() method. Note also this
- // allows cells to be used for multiple columns using colspan
- if ( tfoot !== null ) {
- var cells = oSettings.aoFooter[0];
-
- for ( i=0, ien=cells.length ; i<ien ; i++ ) {
- column = columns[i];
-
- if (column) {
- column.nTf = cells[i].cell;
-
- if ( column.sClass ) {
- $(column.nTf).addClass( column.sClass );
- }
- }
- else {
- _fnLog( oSettings, 0, 'Incorrect column count', 18 );
- }
- }
- }
+ // Every cell needs to be passed through the renderer
+ $(target).children('tr').children('th, td')
+ .each( function () {
+ _fnRenderer( settings, side )(
+ settings, $(this), classes
+ );
+ } );
}
-
/**
- * Draw the header (or footer) element based on the column visibility states. The
- * methodology here is to use the layout array from _fnDetectHeader, modified for
- * the instantaneous column visibility, to construct the new layout. The grid is
- * traversed over cell at a time in a rows x columns grid fashion, although each
- * cell insert can cover multiple elements in the grid - which is tracks using the
- * aApplied array. Cell inserts in the grid will only occur where there isn't
- * already a cell in that position.
- * @param {object} oSettings dataTables settings object
- * @param array {objects} aoSource Layout array from _fnDetectHeader
- * @param {boolean} [bIncludeHidden=false] If true then include the hidden columns in the calc,
- * @memberof DataTable#oApi
+ * Build a layout structure for a header or footer
+ *
+ * @param {*} settings DataTables settings
+ * @param {*} source Source layout array
+ * @param {*} incColumns What columns should be included
+ * @returns Layout array
*/
- function _fnDrawHead( oSettings, aoSource, bIncludeHidden )
+ function _fnHeaderLayout( settings, source, incColumns )
{
- var i, iLen, j, jLen, k, kLen, n, nLocalTr;
- var aoLocal = [];
- var aApplied = [];
- var iColumns = oSettings.aoColumns.length;
- var iRowspan, iColspan;
+ var row, column, cell;
+ var local = [];
+ var structure = [];
+ var columns = settings.aoColumns;
+ var columnCount = columns.length;
+ var rowspan, colspan;
- if ( ! aoSource )
- {
+ if ( ! source ) {
return;
}
- if ( bIncludeHidden === undefined )
- {
- bIncludeHidden = false;
+ // Default is to work on only visible columns
+ if ( ! incColumns ) {
+ incColumns = _range(columnCount)
+ .filter(function (idx) {
+ return columns[idx].bVisible;
+ });
}
- /* Make a copy of the master layout array, but without the visible columns in it */
- for ( i=0, iLen=aoSource.length ; i<iLen ; i++ )
- {
- aoLocal[i] = aoSource[i].slice();
- aoLocal[i].nTr = aoSource[i].nTr;
-
- /* Remove any columns which are currently hidden */
- for ( j=iColumns-1 ; j>=0 ; j-- )
- {
- if ( !oSettings.aoColumns[j].bVisible && !bIncludeHidden )
- {
- aoLocal[i].splice( j, 1 );
- }
- }
+ // Make a copy of the master layout array, but with only the columns we want
+ for ( row=0 ; row<source.length ; row++ ) {
+ // Remove any columns we haven't selected
+ local[row] = source[row].slice().filter(function (cell, i) {
+ return incColumns.includes(i);
+ });
- /* Prep the applied array - it needs an element for each row */
- aApplied.push( [] );
+ // Prep the structure array - it needs an element for each row
+ structure.push( [] );
}
- for ( i=0, iLen=aoLocal.length ; i<iLen ; i++ )
- {
- nLocalTr = aoLocal[i].nTr;
+ for ( row=0 ; row<local.length ; row++ ) {
+ for ( column=0 ; column<local[row].length ; column++ ) {
+ rowspan = 1;
+ colspan = 1;
- /* All cells are going to be replaced, so empty out the row */
- if ( nLocalTr )
- {
- while( (n = nLocalTr.firstChild) )
- {
- nLocalTr.removeChild( n );
+ // Check to see if there is already a cell (row/colspan) covering our target
+ // insert point. If there is, then there is nothing to do.
+ if ( structure[row][column] === undefined ) {
+ cell = local[row][column].cell;
+
+ // Expand for rowspan
+ while (
+ local[row+rowspan] !== undefined &&
+ local[row][column].cell == local[row+rowspan][column].cell
+ ) {
+ structure[row+rowspan][column] = null;
+ rowspan++;
+ }
+
+ // And for colspan
+ while (
+ local[row][column+colspan] !== undefined &&
+ local[row][column].cell == local[row][column+colspan].cell
+ ) {
+ // Which also needs to go over rows
+ for ( var k=0 ; k<rowspan ; k++ ) {
+ structure[row+k][column+colspan] = null;
+ }
+
+ colspan++;
+ }
+
+ var titleSpan = $('span.dt-column-title', cell);
+
+ structure[row][column] = {
+ cell: cell,
+ colspan: colspan,
+ rowspan: rowspan,
+ title: titleSpan.length
+ ? titleSpan.html()
+ : $(cell).html()
+ };
}
}
+ }
- for ( j=0, jLen=aoLocal[i].length ; j<jLen ; j++ )
- {
- iRowspan = 1;
- iColspan = 1;
+ return structure;
+ }
- /* Check to see if there is already a cell (row/colspan) covering our target
- * insert point. If there is, then there is nothing to do.
- */
- if ( aApplied[i][j] === undefined )
- {
- nLocalTr.appendChild( aoLocal[i][j].cell );
- aApplied[i][j] = 1;
- /* Expand the cell to cover as many rows as needed */
- while ( aoLocal[i+iRowspan] !== undefined &&
- aoLocal[i][j].cell == aoLocal[i+iRowspan][j].cell )
- {
- aApplied[i+iRowspan][j] = 1;
- iRowspan++;
- }
+ /**
+ * Draw the header (or footer) element based on the column visibility states.
+ *
+ * @param object oSettings dataTables settings object
+ * @param array aoSource Layout array from _fnDetectHeader
+ * @memberof DataTable#oApi
+ */
+ function _fnDrawHead( settings, source )
+ {
+ var layout = _fnHeaderLayout(settings, source);
+ var tr, n;
- /* Expand the cell to cover as many columns as needed */
- while ( aoLocal[i][j+iColspan] !== undefined &&
- aoLocal[i][j].cell == aoLocal[i][j+iColspan].cell )
- {
- /* Must update the applied array over the rows for the columns */
- for ( k=0 ; k<iRowspan ; k++ )
- {
- aApplied[i+k][j+iColspan] = 1;
- }
- iColspan++;
- }
+ for ( var row=0 ; row<source.length ; row++ ) {
+ tr = source[row].row;
+
+ // All cells are going to be replaced, so empty out the row
+ // Can't use $().empty() as that kills event handlers
+ if (tr) {
+ while( (n = tr.firstChild) ) {
+ tr.removeChild( n );
+ }
+ }
- /* Do the actual expansion in the DOM */
- $(aoLocal[i][j].cell)
- .attr('rowspan', iRowspan)
- .attr('colspan', iColspan);
+ for ( var column=0 ; column<layout[row].length ; column++ ) {
+ var point = layout[row][column];
+
+ if (point) {
+ $(point.cell)
+ .appendTo(tr)
+ .attr('rowspan', point.rowspan)
+ .attr('colspan', point.colspan);
}
}
}
@@ -3490,7 +3369,7 @@
/* Provide a pre-callback function which can be used to cancel the draw is false is returned */
var aPreDraw = _fnCallbackFire( oSettings, 'aoPreDrawCallback', 'preDraw', [oSettings] );
- if ( $.inArray( false, aPreDraw ) !== -1 )
+ if ( aPreDraw.indexOf(false) !== -1 )
{
_fnProcessingDisplay( oSettings, false );
return;
@@ -3498,29 +3377,27 @@
var anRows = [];
var iRowCount = 0;
- var asStripeClasses = oSettings.asStripeClasses;
- var iStripes = asStripeClasses.length;
- var oLang = oSettings.oLanguage;
var bServerSide = _fnDataSource( oSettings ) == 'ssp';
var aiDisplay = oSettings.aiDisplay;
var iDisplayStart = oSettings._iDisplayStart;
var iDisplayEnd = oSettings.fnDisplayEnd();
+ var columns = oSettings.aoColumns;
+ var body = $(oSettings.nTBody);
oSettings.bDrawing = true;
/* Server-side processing draw intercept */
- if ( oSettings.bDeferLoading )
- {
- oSettings.bDeferLoading = false;
- oSettings.iDraw++;
- _fnProcessingDisplay( oSettings, false );
- }
- else if ( !bServerSide )
+ if ( !bServerSide )
{
oSettings.iDraw++;
}
else if ( !oSettings.bDestroying && !ajaxComplete)
{
+ // Show loading message for server-side processing
+ if (oSettings.iDraw === 0) {
+ body.empty().append(_emptyRow(oSettings));
+ }
+
_fnAjaxUpdate( oSettings );
return;
}
@@ -3541,15 +3418,14 @@
var nRow = aoData.nTr;
- /* Remove the old striping classes and then add the new one */
- if ( iStripes !== 0 )
- {
- var sStripe = asStripeClasses[ iRowCount % iStripes ];
- if ( aoData._sRowStripe != sStripe )
- {
- $(nRow).removeClass( aoData._sRowStripe ).addClass( sStripe );
- aoData._sRowStripe = sStripe;
- }
+ // Add various classes as needed
+ for (var i=0 ; i<columns.length ; i++) {
+ var col = columns[i];
+ var td = aoData.anCells[i];
+
+ _addClass(td, _ext.type.className[col.sType]); // auto class
+ _addClass(td, col.sClass); // column class
+ _addClass(td, oSettings.oClasses.tbody.cell); // all cells
}
// Row callback functions - might want to manipulate the row
@@ -3564,23 +3440,7 @@
}
else
{
- /* Table is empty - create a row with an empty message in it */
- var sZero = oLang.sZeroRecords;
- if ( oSettings.iDraw == 1 && _fnDataSource( oSettings ) == 'ajax' )
- {
- sZero = oLang.sLoadingRecords;
- }
- else if ( oLang.sEmptyTable && oSettings.fnRecordsTotal() === 0 )
- {
- sZero = oLang.sEmptyTable;
- }
-
- anRows[ 0 ] = $( '<tr/>', { 'class': iStripes ? asStripeClasses[0] : '' } )
- .append( $('<td />', {
- 'valign': 'top',
- 'colSpan': _fnVisbleColumns( oSettings ),
- 'class': oSettings.oClasses.sRowEmpty
- } ).html( sZero ) )[0];
+ anRows[ 0 ] = _emptyRow(oSettings);
}
/* Header and footer callbacks */
@@ -3590,13 +3450,21 @@
_fnCallbackFire( oSettings, 'aoFooterCallback', 'footer', [ $(oSettings.nTFoot).children('tr')[0],
_fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] );
- var body = $(oSettings.nTBody);
+ // replaceChildren is faster, but only became widespread in 2020,
+ // so a fall back in jQuery is provided for older browsers.
+ if (body[0].replaceChildren) {
+ body[0].replaceChildren.apply(body[0], anRows);
+ }
+ else {
+ body.children().detach();
+ body.append( $(anRows) );
+ }
- body.children().detach();
- body.append( $(anRows) );
+ // Empty table needs a specific class
+ $(oSettings.nTableWrapper).toggleClass('dt-empty-footer', $('tr', oSettings.nTFoot).length === 0);
/* Call all required callback functions for the end of a draw */
- _fnCallbackFire( oSettings, 'aoDrawCallback', 'draw', [oSettings] );
+ _fnCallbackFire( oSettings, 'aoDrawCallback', 'draw', [oSettings], true );
/* Draw is complete, sorting and filtering must be as well */
oSettings.bSorted = false;
@@ -3612,23 +3480,25 @@
* the paging is reset to the first page
* @memberof DataTable#oApi
*/
- function _fnReDraw( settings, holdPosition )
+ function _fnReDraw( settings, holdPosition, recompute )
{
var
features = settings.oFeatures,
sort = features.bSort,
filter = features.bFilter;
- if ( sort ) {
- _fnSort( settings );
- }
+ if (recompute === undefined || recompute === true) {
+ if ( sort ) {
+ _fnSort( settings );
+ }
- if ( filter ) {
- _fnFilterComplete( settings, settings.oPreviousSearch );
- }
- else {
- // No filtering, so we want to just use the display master
- settings.aiDisplay = settings.aiDisplayMaster.slice();
+ if ( filter ) {
+ _fnFilterComplete( settings, settings.oPreviousSearch );
+ }
+ else {
+ // No filtering, so we want to just use the display master
+ settings.aiDisplay = settings.aiDisplayMaster.slice();
+ }
}
if ( holdPosition !== true ) {
@@ -3645,155 +3515,324 @@
}
+ /*
+ * Table is empty - create a row with an empty message in it
+ */
+ function _emptyRow ( settings ) {
+ var oLang = settings.oLanguage;
+ var zero = oLang.sZeroRecords;
+ var dataSrc = _fnDataSource( settings );
+
+ if (
+ (settings.iDraw < 1 && dataSrc === 'ssp') ||
+ (settings.iDraw <= 1 && dataSrc === 'ajax')
+ ) {
+ zero = oLang.sLoadingRecords;
+ }
+ else if ( oLang.sEmptyTable && settings.fnRecordsTotal() === 0 )
+ {
+ zero = oLang.sEmptyTable;
+ }
+
+ return $( '<tr/>' )
+ .append( $('<td />', {
+ 'colSpan': _fnVisbleColumns( settings ),
+ 'class': settings.oClasses.empty.row
+ } ).html( zero ) )[0];
+ }
+
+
+ /**
+ * Convert a `layout` object given by a user to the object structure needed
+ * for the renderer. This is done twice, once for above and once for below
+ * the table. Ordering must also be considered.
+ *
+ * @param {*} settings DataTables settings object
+ * @param {*} layout Layout object to convert
+ * @param {string} side `top` or `bottom`
+ * @returns Converted array structure - one item for each row.
+ */
+ function _layoutArray ( settings, layout, side )
+ {
+ var groups = {};
+
+ // Combine into like groups (e.g. `top`, `top2`, etc)
+ $.each( layout, function ( pos, val ) {
+ if (val === null) {
+ return;
+ }
+
+ var splitPos = pos.replace(/([A-Z])/g, ' $1').split(' ');
+
+ if ( ! groups[ splitPos[0] ] ) {
+ groups[ splitPos[0] ] = {};
+ }
+
+ var align = splitPos.length === 1 ?
+ 'full' :
+ splitPos[1].toLowerCase();
+ var group = groups[ splitPos[0] ];
+ var groupRun = function (contents, innerVal) {
+ // If it is an object, then there can be multiple features contained in it
+ if ( $.isPlainObject( innerVal ) ) {
+ Object.keys(innerVal).map(function (key) {
+ contents.push( {
+ feature: key,
+ opts: innerVal[key]
+ });
+ });
+ }
+ else {
+ contents.push(innerVal);
+ }
+ }
+
+ // Transform to an object with a contents property
+ if (! group[align] || ! group[align].contents) {
+ group[align] = { contents: [] };
+ }
+
+ // Allow for an array or just a single object
+ if ( Array.isArray(val)) {
+ for (var i=0 ; i<val.length ; i++) {
+ groupRun(group[align].contents, val[i]);
+ }
+ }
+ else {
+ groupRun(group[ align ].contents, val);
+ }
+
+ // And make contents an array
+ if ( ! Array.isArray( group[ align ].contents ) ) {
+ group[ align ].contents = [ group[ align ].contents ];
+ }
+ } );
+
+ var filtered = Object.keys(groups)
+ .map( function ( pos ) {
+ // Filter to only the side we need
+ if ( pos.indexOf(side) !== 0 ) {
+ return null;
+ }
+
+ return {
+ name: pos,
+ val: groups[pos]
+ };
+ } )
+ .filter( function (item) {
+ return item !== null;
+ });
+
+ // Order by item identifier
+ filtered.sort( function ( a, b ) {
+ var order1 = a.name.replace(/[^0-9]/g, '') * 1;
+ var order2 = b.name.replace(/[^0-9]/g, '') * 1;
+
+ return order2 - order1;
+ } );
+
+ if ( side === 'bottom' ) {
+ filtered.reverse();
+ }
+
+ // Split into rows
+ var rows = [];
+ for ( var i=0, ien=filtered.length ; i<ien ; i++ ) {
+ if ( filtered[i].val.full ) {
+ rows.push( { full: filtered[i].val.full } );
+ _layoutResolve( settings, rows[ rows.length - 1 ] );
+
+ delete filtered[i].val.full;
+ }
+
+ if ( Object.keys(filtered[i].val).length ) {
+ rows.push( filtered[i].val );
+ _layoutResolve( settings, rows[ rows.length - 1 ] );
+ }
+ }
+
+ return rows;
+ }
+
+
+ /**
+ * Convert the contents of a row's layout object to nodes that can be inserted
+ * into the document by a renderer. Execute functions, look up plug-ins, etc.
+ *
+ * @param {*} settings DataTables settings object
+ * @param {*} row Layout object for this row
+ */
+ function _layoutResolve( settings, row ) {
+ var getFeature = function (feature, opts) {
+ if ( ! _ext.features[ feature ] ) {
+ _fnLog( settings, 0, 'Unknown feature: '+ feature );
+ }
+
+ return _ext.features[ feature ].apply( this, [settings, opts] );
+ };
+
+ var resolve = function ( item ) {
+ var line = row[ item ].contents;
+
+ for ( var i=0, ien=line.length ; i<ien ; i++ ) {
+ if ( ! line[i] ) {
+ continue;
+ }
+ else if ( typeof line[i] === 'string' ) {
+ line[i] = getFeature( line[i], null );
+ }
+ else if ( $.isPlainObject(line[i]) ) {
+ // If it's an object, it just has feature and opts properties from
+ // the transform in _layoutArray
+ line[i] = getFeature(line[i].feature, line[i].opts);
+ }
+ else if ( typeof line[i].node === 'function' ) {
+ line[i] = line[i].node( settings );
+ }
+ else if ( typeof line[i] === 'function' ) {
+ var inst = line[i]( settings );
+
+ line[i] = typeof inst.node === 'function' ?
+ inst.node() :
+ inst;
+ }
+ }
+ };
+
+ $.each( row, function ( key ) {
+ resolve( key );
+ } );
+ }
+
+
/**
* Add the options to the page HTML for the table
- * @param {object} oSettings dataTables settings object
+ * @param {object} settings DataTables settings object
* @memberof DataTable#oApi
*/
- function _fnAddOptionsHtml ( oSettings )
+ function _fnAddOptionsHtml ( settings )
{
- var classes = oSettings.oClasses;
- var table = $(oSettings.nTable);
- var holding = $('<div/>').insertBefore( table ); // Holding element for speed
- var features = oSettings.oFeatures;
-
- // All DataTables are wrapped in a div
- var insert = $('<div/>', {
- id: oSettings.sTableId+'_wrapper',
- 'class': classes.sWrapper + (oSettings.nTFoot ? '' : ' '+classes.sNoFooter)
- } );
+ var classes = settings.oClasses;
+ var table = $(settings.nTable);
- oSettings.nHolding = holding[0];
- oSettings.nTableWrapper = insert[0];
- oSettings.nTableReinsertBefore = oSettings.nTable.nextSibling;
+ // Wrapper div around everything DataTables controls
+ var insert = $('<div/>')
+ .attr({
+ id: settings.sTableId+'_wrapper',
+ 'class': classes.container
+ })
+ .insertBefore(table);
- /* Loop over the user set positioning and place the elements as needed */
- var aDom = oSettings.sDom.split('');
- var featureNode, cOption, nNewNode, cNext, sAttr, j;
- for ( var i=0 ; i<aDom.length ; i++ )
- {
+ settings.nTableWrapper = insert[0];
+
+ if (settings.sDom) {
+ // Legacy
+ _fnLayoutDom(settings, settings.sDom, insert);
+ }
+ else {
+ var top = _layoutArray( settings, settings.layout, 'top' );
+ var bottom = _layoutArray( settings, settings.layout, 'bottom' );
+ var renderer = _fnRenderer( settings, 'layout' );
+
+ // Everything above - the renderer will actually insert the contents into the document
+ top.forEach(function (item) {
+ renderer( settings, insert, item );
+ });
+
+ // The table - always the center of attention
+ renderer( settings, insert, {
+ full: {
+ table: true,
+ contents: [ _fnFeatureHtmlTable(settings) ]
+ }
+ } );
+
+ // Everything below
+ bottom.forEach(function (item) {
+ renderer( settings, insert, item );
+ });
+ }
+
+ // Processing floats on top, so it isn't an inserted feature
+ _processingHtml( settings );
+ }
+
+ /**
+ * Draw the table with the legacy DOM property
+ * @param {*} settings DT settings object
+ * @param {*} dom DOM string
+ * @param {*} insert Insert point
+ */
+ function _fnLayoutDom( settings, dom, insert )
+ {
+ var parts = dom.match(/(".*?")|('.*?')|./g);
+ var featureNode, option, newNode, next, attr;
+
+ for ( var i=0 ; i<parts.length ; i++ ) {
featureNode = null;
- cOption = aDom[i];
+ option = parts[i];
- if ( cOption == '<' )
- {
- /* New container div */
- nNewNode = $('<div/>')[0];
+ if ( option == '<' ) {
+ // New container div
+ newNode = $('<div/>');
- /* Check to see if we should append an id and/or a class name to the container */
- cNext = aDom[i+1];
- if ( cNext == "'" || cNext == '"' )
- {
- sAttr = "";
- j = 2;
- while ( aDom[i+j] != cNext )
- {
- sAttr += aDom[i+j];
- j++;
- }
+ // Check to see if we should append an id and/or a class name to the container
+ next = parts[i+1];
- /* Replace jQuery UI constants @todo depreciated */
- if ( sAttr == "H" )
- {
- sAttr = classes.sJUIHeader;
- }
- else if ( sAttr == "F" )
- {
- sAttr = classes.sJUIFooter;
- }
+ if ( next[0] == "'" || next[0] == '"' ) {
+ attr = next.replace(/['"]/g, '');
+
+ var id = '', className;
/* The attribute can be in the format of "#id.class", "#id" or "class" This logic
* breaks the string into parts and applies them as needed
*/
- if ( sAttr.indexOf('.') != -1 )
- {
- var aSplit = sAttr.split('.');
- nNewNode.id = aSplit[0].substr(1, aSplit[0].length-1);
- nNewNode.className = aSplit[1];
+ if ( attr.indexOf('.') != -1 ) {
+ var split = attr.split('.');
+
+ id = split[0];
+ className = split[1];
}
- else if ( sAttr.charAt(0) == "#" )
- {
- nNewNode.id = sAttr.substr(1, sAttr.length-1);
+ else if ( attr[0] == "#" ) {
+ id = attr;
}
- else
- {
- nNewNode.className = sAttr;
+ else {
+ className = attr;
}
- i += j; /* Move along the position array */
+ newNode
+ .attr('id', id.substring(1))
+ .addClass(className);
+
+ i++; // Move along the position array
}
- insert.append( nNewNode );
- insert = $(nNewNode);
+ insert.append( newNode );
+ insert = newNode;
}
- else if ( cOption == '>' )
- {
- /* End container div */
+ else if ( option == '>' ) {
+ // End container div
insert = insert.parent();
}
- // @todo Move options into their own plugins?
- else if ( cOption == 'l' && features.bPaginate && features.bLengthChange )
- {
- /* Length */
- featureNode = _fnFeatureHtmlLength( oSettings );
- }
- else if ( cOption == 'f' && features.bFilter )
- {
- /* Filter */
- featureNode = _fnFeatureHtmlFilter( oSettings );
- }
- else if ( cOption == 'r' && features.bProcessing )
- {
- /* pRocessing */
- featureNode = _fnFeatureHtmlProcessing( oSettings );
- }
- else if ( cOption == 't' )
- {
- /* Table */
- featureNode = _fnFeatureHtmlTable( oSettings );
- }
- else if ( cOption == 'i' && features.bInfo )
- {
- /* Info */
- featureNode = _fnFeatureHtmlInfo( oSettings );
- }
- else if ( cOption == 'p' && features.bPaginate )
- {
- /* Pagination */
- featureNode = _fnFeatureHtmlPaginate( oSettings );
+ else if ( option == 't' ) {
+ // Table
+ featureNode = _fnFeatureHtmlTable( settings );
}
- else if ( DataTable.ext.feature.length !== 0 )
+ else
{
- /* Plug-in features */
- var aoFeatures = DataTable.ext.feature;
- for ( var k=0, kLen=aoFeatures.length ; k<kLen ; k++ )
- {
- if ( cOption == aoFeatures[k].cFeature )
- {
- featureNode = aoFeatures[k].fnInit( oSettings );
- break;
+ DataTable.ext.feature.forEach(function(feature) {
+ if ( option == feature.cFeature ) {
+ featureNode = feature.fnInit( settings );
}
- }
+ });
}
- /* Add to the 2D features array */
- if ( featureNode )
- {
- var aanFeatures = oSettings.aanFeatures;
-
- if ( ! aanFeatures[cOption] )
- {
- aanFeatures[cOption] = [];
- }
-
- aanFeatures[cOption].push( featureNode );
+ // Add to the display
+ if ( featureNode ) {
insert.append( featureNode );
}
}
-
- /* Built our DOM structure - replace the holding div with what we want */
- holding.replaceWith( insert );
- oSettings.nHolding = null;
}
@@ -3802,111 +3841,147 @@
* create a layout grid (array) of rows x columns, which contains a reference
* to the cell that that point in the grid (regardless of col/rowspan), such that
* any column / row could be removed and the new grid constructed
- * @param array {object} aLayout Array to store the calculated layout in
- * @param {node} nThead The header/footer element for the table
+ * @param {node} thead The header/footer element for the table
+ * @returns {array} Calculated layout array
* @memberof DataTable#oApi
*/
- function _fnDetectHeader ( aLayout, nThead )
+ function _fnDetectHeader ( settings, thead, write )
{
- var nTrs = $(nThead).children('tr');
- var nTr, nCell;
- var i, k, l, iLen, jLen, iColShifted, iColumn, iColspan, iRowspan;
- var bUnique;
- var fnShiftCol = function ( a, i, j ) {
+ var columns = settings.aoColumns;
+ var rows = $(thead).children('tr');
+ var row, cell;
+ var i, k, l, iLen, shifted, column, colspan, rowspan;
+ var isHeader = thead && thead.nodeName.toLowerCase() === 'thead';
+ var layout = [];
+ var unique;
+ var shift = function ( a, i, j ) {
var k = a[i];
- while ( k[j] ) {
+ while ( k[j] ) {
j++;
}
return j;
};
- aLayout.splice( 0, aLayout.length );
-
- /* We know how many rows there are in the layout - so prep it */
- for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
- {
- aLayout.push( [] );
+ // We know how many rows there are in the layout - so prep it
+ for ( i=0, iLen=rows.length ; i<iLen ; i++ ) {
+ layout.push( [] );
}
- /* Calculate a layout array */
- for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
- {
- nTr = nTrs[i];
- iColumn = 0;
-
- /* For every cell in the row... */
- nCell = nTr.firstChild;
- while ( nCell ) {
- if ( nCell.nodeName.toUpperCase() == "TD" ||
- nCell.nodeName.toUpperCase() == "TH" )
- {
- /* Get the col and rowspan attributes from the DOM and sanitise them */
- iColspan = nCell.getAttribute('colspan') * 1;
- iRowspan = nCell.getAttribute('rowspan') * 1;
- iColspan = (!iColspan || iColspan===0 || iColspan===1) ? 1 : iColspan;
- iRowspan = (!iRowspan || iRowspan===0 || iRowspan===1) ? 1 : iRowspan;
-
- /* There might be colspan cells already in this row, so shift our target
- * accordingly
- */
- iColShifted = fnShiftCol( aLayout, i, iColumn );
+ for ( i=0, iLen=rows.length ; i<iLen ; i++ ) {
+ row = rows[i];
+ column = 0;
- /* Cache calculation for unique columns */
- bUnique = iColspan === 1 ? true : false;
+ // For every cell in the row..
+ cell = row.firstChild;
+ while ( cell ) {
+ if (
+ cell.nodeName.toUpperCase() == 'TD' ||
+ cell.nodeName.toUpperCase() == 'TH'
+ ) {
+ var cols = [];
+
+ // Get the col and rowspan attributes from the DOM and sanitise them
+ colspan = cell.getAttribute('colspan') * 1;
+ rowspan = cell.getAttribute('rowspan') * 1;
+ colspan = (!colspan || colspan===0 || colspan===1) ? 1 : colspan;
+ rowspan = (!rowspan || rowspan===0 || rowspan===1) ? 1 : rowspan;
+
+ // There might be colspan cells already in this row, so shift our target
+ // accordingly
+ shifted = shift( layout, i, column );
+
+ // Cache calculation for unique columns
+ unique = colspan === 1 ?
+ true :
+ false;
+
+ // Perform header setup
+ if ( write ) {
+ if (unique) {
+ // Allow column options to be set from HTML attributes
+ _fnColumnOptions( settings, shifted, $(cell).data() );
+
+ // Get the width for the column. This can be defined from the
+ // width attribute, style attribute or `columns.width` option
+ var columnDef = columns[shifted];
+ var width = cell.getAttribute('width') || null;
+ var t = cell.style.width.match(/width:\s*(\d+[pxem%]+)/);
+ if ( t ) {
+ width = t[1];
+ }
- /* If there is col / rowspan, copy the information into the layout grid */
- for ( l=0 ; l<iColspan ; l++ )
- {
- for ( k=0 ; k<iRowspan ; k++ )
- {
- aLayout[i+k][iColShifted+l] = {
- "cell": nCell,
- "unique": bUnique
- };
- aLayout[i+k].nTr = nTr;
+ columnDef.sWidthOrig = columnDef.sWidth || width;
+
+ if (isHeader) {
+ // Column title handling - can be user set, or read from the DOM
+ // This happens before the render, so the original is still in place
+ if ( columnDef.sTitle !== null && ! columnDef.autoTitle ) {
+ cell.innerHTML = columnDef.sTitle;
+ }
+
+ if (! columnDef.sTitle && unique) {
+ columnDef.sTitle = _stripHtml(cell.innerHTML);
+ columnDef.autoTitle = true;
+ }
+ }
+ else {
+ // Footer specific operations
+ if (columnDef.footer) {
+ cell.innerHTML = columnDef.footer;
+ }
+ }
+
+ // Fall back to the aria-label attribute on the table header if no ariaTitle is
+ // provided.
+ if (! columnDef.ariaTitle) {
+ columnDef.ariaTitle = $(cell).attr("aria-label") || columnDef.sTitle;
+ }
+
+ // Column specific class names
+ if ( columnDef.className ) {
+ $(cell).addClass( columnDef.className );
+ }
+ }
+
+ // Wrap the column title so we can write to it in future
+ if ( $('span.dt-column-title', cell).length === 0) {
+ $('<span>')
+ .addClass('dt-column-title')
+ .append(cell.childNodes)
+ .appendTo(cell);
+ }
+
+ if ( isHeader && $('span.dt-column-order', cell).length === 0) {
+ $('<span>')
+ .addClass('dt-column-order')
+ .appendTo(cell);
}
}
- }
- nCell = nCell.nextSibling;
- }
- }
- }
+ // If there is col / rowspan, copy the information into the layout grid
+ for ( l=0 ; l<colspan ; l++ ) {
+ for ( k=0 ; k<rowspan ; k++ ) {
+ layout[i+k][shifted+l] = {
+ cell: cell,
+ unique: unique
+ };
- /**
- * Get an array of unique th elements, one for each column
- * @param {object} oSettings dataTables settings object
- * @param {node} nHeader automatically detect the layout from this node - optional
- * @param {array} aLayout thead/tfoot layout from _fnDetectHeader - optional
- * @returns array {node} aReturn list of unique th's
- * @memberof DataTable#oApi
- */
- function _fnGetUniqueThs ( oSettings, nHeader, aLayout )
- {
- var aReturn = [];
- if ( !aLayout )
- {
- aLayout = oSettings.aoHeader;
- if ( nHeader )
- {
- aLayout = [];
- _fnDetectHeader( aLayout, nHeader );
- }
- }
+ layout[i+k].row = row;
+ }
- for ( var i=0, iLen=aLayout.length ; i<iLen ; i++ )
- {
- for ( var j=0, jLen=aLayout[i].length ; j<jLen ; j++ )
- {
- if ( aLayout[i][j].unique &&
- (!aReturn[j] || !oSettings.bSortCellsTop) )
- {
- aReturn[j] = aLayout[i][j].cell;
+ cols.push( shifted+l );
+ }
+
+ // Assign an attribute so spanning cells can still be identified
+ // as belonging to a column
+ cell.setAttribute('data-dt-column', _unique(cols).join(','));
}
+
+ cell = cell.nextSibling;
}
}
- return aReturn;
+ return layout;
}
/**
@@ -3942,34 +4017,6 @@
*/
function _fnBuildAjax( oSettings, data, fn )
{
- // Compatibility with 1.9-, allow fnServerData and event to manipulate
- _fnCallbackFire( oSettings, 'aoServerParams', 'serverParams', [data] );
-
- // Convert to object based for 1.10+ if using the old array scheme which can
- // come from server-side processing or serverParams
- if ( data && Array.isArray(data) ) {
- var tmp = {};
- var rbracket = /(.*?)\[\]$/;
-
- $.each( data, function (key, val) {
- var match = val.name.match(rbracket);
-
- if ( match ) {
- // Support for arrays
- var name = match[0];
-
- if ( ! tmp[ name ] ) {
- tmp[ name ] = [];
- }
- tmp[ name ].push( val.value );
- }
- else {
- tmp[val.name] = val.value;
- }
- } );
- data = tmp;
- }
-
var ajaxData;
var ajax = oSettings.ajax;
var instance = oSettings.oInstance;
@@ -3990,7 +4037,7 @@
oSettings.json = json;
- _fnCallbackFire( oSettings, null, 'xhr', [oSettings, json, oSettings.jqXHR] );
+ _fnCallbackFire( oSettings, null, 'xhr', [oSettings, json, oSettings.jqXHR], true );
fn( json );
};
@@ -4013,15 +4060,18 @@
}
var baseAjax = {
+ "url": typeof ajax === 'string' ?
+ ajax :
+ '',
"data": data,
"success": callback,
"dataType": "json",
"cache": false,
"type": oSettings.sServerMethod,
- "error": function (xhr, error, thrown) {
- var ret = _fnCallbackFire( oSettings, null, 'xhr', [oSettings, null, oSettings.jqXHR] );
+ "error": function (xhr, error) {
+ var ret = _fnCallbackFire( oSettings, null, 'xhr', [oSettings, null, oSettings.jqXHR], true );
- if ( $.inArray( true, ret ) === -1 ) {
+ if ( ret.indexOf(true) === -1 ) {
if ( error == "parsererror" ) {
_fnLog( oSettings, 0, 'Invalid JSON response', 1 );
}
@@ -4034,43 +4084,38 @@
}
};
+ // If `ajax` option is an object, extend and override our default base
+ if ( $.isPlainObject( ajax ) ) {
+ $.extend( baseAjax, ajax )
+ }
+
// Store the data submitted for the API
oSettings.oAjaxData = data;
// Allow plug-ins and external processes to modify the data
- _fnCallbackFire( oSettings, null, 'preXhr', [oSettings, data] );
+ _fnCallbackFire( oSettings, null, 'preXhr', [oSettings, data, baseAjax], true );
- if ( oSettings.fnServerData )
- {
- // DataTables 1.9- compatibility
- oSettings.fnServerData.call( instance,
- oSettings.sAjaxSource,
- $.map( data, function (val, key) { // Need to convert back to 1.9 trad format
- return { name: key, value: val };
- } ),
- callback,
- oSettings
- );
- }
- else if ( oSettings.sAjaxSource || typeof ajax === 'string' )
- {
- // DataTables 1.9- compatibility
- oSettings.jqXHR = $.ajax( $.extend( baseAjax, {
- url: ajax || oSettings.sAjaxSource
- } ) );
- }
- else if ( typeof ajax === 'function' )
+ if ( typeof ajax === 'function' )
{
// Is a function - let the caller define what needs to be done
oSettings.jqXHR = ajax.call( instance, data, callback, oSettings );
}
- else
- {
+ else if (ajax.url === '') {
+ // No url, so don't load any data. Just apply an empty data array
+ // to the object for the callback.
+ var empty = {};
+
+ DataTable.util.set(ajax.dataSrc)(empty, []);
+ callback(empty);
+ }
+ else {
// Object to extend the base settings
- oSettings.jqXHR = $.ajax( $.extend( baseAjax, ajax ) );
+ oSettings.jqXHR = $.ajax( baseAjax );
// Restore for next time around
- ajax.data = ajaxData;
+ if ( ajaxData ) {
+ ajax.data = ajaxData;
+ }
}
}
@@ -4086,16 +4131,11 @@
settings.iDraw++;
_fnProcessingDisplay( settings, true );
- // Keep track of drawHold state to handle scrolling after the Ajax call
- var drawHold = settings._drawHold;
-
_fnBuildAjax(
settings,
_fnAjaxParameters( settings ),
function(json) {
- settings._drawHold = drawHold;
_fnAjaxUpdateDraw( settings, json );
- settings._drawHold = false;
}
);
}
@@ -4103,11 +4143,7 @@
/**
* Build up the parameters in an object needed for a server-side processing
- * request. Note that this is basically done twice, is different ways - a modern
- * method which is used by default in DataTables 1.10 which uses objects and
- * arrays, or the 1.9- method with is name / value pairs. 1.9 method is used if
- * the sAjaxSource option is used in the initialisation, or the legacyAjax
- * option is set.
+ * request.
* @param {object} oSettings dataTables settings object
* @returns {bool} block the table drawing or not
* @memberof DataTable#oApi
@@ -4116,96 +4152,57 @@
{
var
columns = settings.aoColumns,
- columnCount = columns.length,
features = settings.oFeatures,
preSearch = settings.oPreviousSearch,
preColSearch = settings.aoPreSearchCols,
- i, data = [], dataProp, column, columnSearch,
- sort = _fnSortFlatten( settings ),
- displayStart = settings._iDisplayStart,
- displayLength = features.bPaginate !== false ?
+ colData = function ( idx, prop ) {
+ return typeof columns[idx][prop] === 'function' ?
+ 'function' :
+ columns[idx][prop];
+ };
+
+ return {
+ draw: settings.iDraw,
+ columns: columns.map( function ( column, i ) {
+ return {
+ data: colData(i, 'mData'),
+ name: column.sName,
+ searchable: column.bSearchable,
+ orderable: column.bSortable,
+ search: {
+ value: preColSearch[i].search,
+ regex: preColSearch[i].regex,
+ fixed: Object.keys(column.searchFixed).map( function(name) {
+ return {
+ name: name,
+ term: column.searchFixed[name].toString()
+ }
+ })
+ }
+ };
+ } ),
+ order: _fnSortFlatten( settings ).map( function ( val ) {
+ return {
+ column: val.col,
+ dir: val.dir,
+ name: colData(val.col, 'sName')
+ };
+ } ),
+ start: settings._iDisplayStart,
+ length: features.bPaginate ?
settings._iDisplayLength :
- -1;
-
- var param = function ( name, value ) {
- data.push( { 'name': name, 'value': value } );
- };
-
- // DataTables 1.9- compatible method
- param( 'sEcho', settings.iDraw );
- param( 'iColumns', columnCount );
- param( 'sColumns', _pluck( columns, 'sName' ).join(',') );
- param( 'iDisplayStart', displayStart );
- param( 'iDisplayLength', displayLength );
-
- // DataTables 1.10+ method
- var d = {
- draw: settings.iDraw,
- columns: [],
- order: [],
- start: displayStart,
- length: displayLength,
- search: {
- value: preSearch.sSearch,
- regex: preSearch.bRegex
+ -1,
+ search: {
+ value: preSearch.search,
+ regex: preSearch.regex,
+ fixed: Object.keys(settings.searchFixed).map( function(name) {
+ return {
+ name: name,
+ term: settings.searchFixed[name].toString()
+ }
+ })
}
};
-
- for ( i=0 ; i<columnCount ; i++ ) {
- column = columns[i];
- columnSearch = preColSearch[i];
- dataProp = typeof column.mData=="function" ? 'function' : column.mData ;
-
- d.columns.push( {
- data: dataProp,
- name: column.sName,
- searchable: column.bSearchable,
- orderable: column.bSortable,
- search: {
- value: columnSearch.sSearch,
- regex: columnSearch.bRegex
- }
- } );
-
- param( "mDataProp_"+i, dataProp );
-
- if ( features.bFilter ) {
- param( 'sSearch_'+i, columnSearch.sSearch );
- param( 'bRegex_'+i, columnSearch.bRegex );
- param( 'bSearchable_'+i, column.bSearchable );
- }
-
- if ( features.bSort ) {
- param( 'bSortable_'+i, column.bSortable );
- }
- }
-
- if ( features.bFilter ) {
- param( 'sSearch', preSearch.sSearch );
- param( 'bRegex', preSearch.bRegex );
- }
-
- if ( features.bSort ) {
- $.each( sort, function ( i, val ) {
- d.order.push( { column: val.col, dir: val.dir } );
-
- param( 'iSortCol_'+i, val.col );
- param( 'sSortDir_'+i, val.dir );
- } );
-
- param( 'iSortingCols', sort.length );
- }
-
- // If the legacy.ajax parameter is null, then we automatically decide which
- // form to use, based on sAjaxSource
- var legacy = DataTable.ext.legacy.ajax;
- if ( legacy === null ) {
- return settings.sAjaxSource ? data : d;
- }
-
- // Otherwise, if legacy has been specified then we use that to decide on the
- // form
- return legacy ? data : d;
}
@@ -4222,16 +4219,10 @@
*/
function _fnAjaxUpdateDraw ( settings, json )
{
- // v1.10 uses camelCase variables, while 1.9 uses Hungarian notation.
- // Support both
- var compat = function ( old, modern ) {
- return json[old] !== undefined ? json[old] : json[modern];
- };
-
- var data = _fnAjaxDataSrc( settings, json );
- var draw = compat( 'sEcho', 'draw' );
- var recordsTotal = compat( 'iTotalRecords', 'recordsTotal' );
- var recordsFiltered = compat( 'iTotalDisplayRecords', 'recordsFiltered' );
+ var data = _fnAjaxDataSrc(settings, json);
+ var draw = _fnAjaxDataSrcParam(settings, 'draw', json);
+ var recordsTotal = _fnAjaxDataSrcParam(settings, 'recordsTotal', json);
+ var recordsFiltered = _fnAjaxDataSrcParam(settings, 'recordsFiltered', json);
if ( draw !== undefined ) {
// Protect against out of sequence returns
@@ -4256,11 +4247,7 @@
settings.aiDisplay = settings.aiDisplayMaster.slice();
_fnDraw( settings, true );
-
- if ( ! settings._bInitComplete ) {
- _fnInitComplete( settings, json );
- }
-
+ _fnInitComplete( settings );
_fnProcessingDisplay( settings, false );
}
@@ -4269,188 +4256,144 @@
* Get the data from the JSON data source to use for drawing a table. Using
* `_fnGetObjectDataFn` allows the data to be sourced from a property of the
* source object, or from a processing function.
- * @param {object} oSettings dataTables settings object
+ * @param {object} settings dataTables settings object
* @param {object} json Data source object / array from the server
* @return {array} Array of data to use
*/
- function _fnAjaxDataSrc ( oSettings, json, write )
- {
- var dataSrc = $.isPlainObject( oSettings.ajax ) && oSettings.ajax.dataSrc !== undefined ?
- oSettings.ajax.dataSrc :
- oSettings.sAjaxDataProp; // Compatibility with 1.9-.
+ function _fnAjaxDataSrc ( settings, json, write )
+ {
+ var dataProp = 'data';
+
+ if ($.isPlainObject( settings.ajax ) && settings.ajax.dataSrc !== undefined) {
+ // Could in inside a `dataSrc` object, or not!
+ var dataSrc = settings.ajax.dataSrc;
+
+ // string, function and object are valid types
+ if (typeof dataSrc === 'string' || typeof dataSrc === 'function') {
+ dataProp = dataSrc;
+ }
+ else if (dataSrc.data !== undefined) {
+ dataProp = dataSrc.data;
+ }
+ }
if ( ! write ) {
- if ( dataSrc === 'data' ) {
+ if ( dataProp === 'data' ) {
// If the default, then we still want to support the old style, and safely ignore
// it if possible
- return json.aaData || json[dataSrc];
+ return json.aaData || json[dataProp];
}
- return dataSrc !== "" ?
- _fnGetObjectDataFn( dataSrc )( json ) :
+ return dataProp !== "" ?
+ _fnGetObjectDataFn( dataProp )( json ) :
json;
}
-
+
// set
- _fnSetObjectDataFn( dataSrc )( json, write );
+ _fnSetObjectDataFn( dataProp )( json, write );
}
/**
- * Generate the node required for filtering text
- * @returns {node} Filter control element
- * @param {object} oSettings dataTables settings object
- * @memberof DataTable#oApi
+ * Very similar to _fnAjaxDataSrc, but for the other SSP properties
+ * @param {*} settings DataTables settings object
+ * @param {*} param Target parameter
+ * @param {*} json JSON data
+ * @returns Resolved value
*/
- function _fnFeatureHtmlFilter ( settings )
- {
- var classes = settings.oClasses;
- var tableId = settings.sTableId;
- var language = settings.oLanguage;
- var previousSearch = settings.oPreviousSearch;
- var features = settings.aanFeatures;
- var input = '<input type="search" class="'+classes.sFilterInput+'"/>';
-
- var str = language.sSearch;
- str = str.match(/_INPUT_/) ?
- str.replace('_INPUT_', input) :
- str+input;
+ function _fnAjaxDataSrcParam (settings, param, json) {
+ var dataSrc = $.isPlainObject( settings.ajax )
+ ? settings.ajax.dataSrc
+ : null;
- var filter = $('<div/>', {
- 'id': ! features.f ? tableId+'_filter' : null,
- 'class': classes.sFilter
- } )
- .append( $('<label/>' ).append( str ) );
-
- var searchFn = function(event) {
- /* Update all other filter input elements for the new display */
- var n = features.f;
- var val = !this.value ? "" : this.value; // mental IE8 fix :-(
- if(previousSearch.return && event.key !== "Enter") {
- return;
- }
- /* Now do the filter */
- if ( val != previousSearch.sSearch ) {
- _fnFilterComplete( settings, {
- "sSearch": val,
- "bRegex": previousSearch.bRegex,
- "bSmart": previousSearch.bSmart ,
- "bCaseInsensitive": previousSearch.bCaseInsensitive,
- "return": previousSearch.return
- } );
-
- // Need to redraw, without resorting
- settings._iDisplayStart = 0;
- _fnDraw( settings );
- }
- };
-
- var searchDelay = settings.searchDelay !== null ?
- settings.searchDelay :
- _fnDataSource( settings ) === 'ssp' ?
- 400 :
- 0;
+ if (dataSrc && dataSrc[param]) {
+ // Get from custom location
+ return _fnGetObjectDataFn( dataSrc[param] )( json );
+ }
- var jqFilter = $('input', filter)
- .val( previousSearch.sSearch )
- .attr( 'placeholder', language.sSearchPlaceholder )
- .on(
- 'keyup.DT search.DT input.DT paste.DT cut.DT',
- searchDelay ?
- _fnThrottle( searchFn, searchDelay ) :
- searchFn
- )
- .on( 'mouseup.DT', function(e) {
- // Edge fix! Edge 17 does not trigger anything other than mouse events when clicking
- // on the clear icon (Edge bug 17584515). This is safe in other browsers as `searchFn`
- // checks the value to see if it has changed. In other browsers it won't have.
- setTimeout( function () {
- searchFn.call(jqFilter[0], e);
- }, 10);
- } )
- .on( 'keypress.DT', function(e) {
- /* Prevent form submission */
- if ( e.keyCode == 13 ) {
- return false;
- }
- } )
- .attr('aria-controls', tableId);
+ // else - Default behaviour
+ var old = '';
- // Update the input elements whenever the table is filtered
- $(settings.nTable).on( 'search.dt.DT', function ( ev, s ) {
- if ( settings === s ) {
- // IE9 throws an 'unknown error' if document.activeElement is used
- // inside an iframe or frame...
- try {
- if ( jqFilter[0] !== document.activeElement ) {
- jqFilter.val( previousSearch.sSearch );
- }
- }
- catch ( e ) {}
- }
- } );
+ // Legacy support
+ if (param === 'draw') {
+ old = 'sEcho';
+ }
+ else if (param === 'recordsTotal') {
+ old = 'iTotalRecords';
+ }
+ else if (param === 'recordsFiltered') {
+ old = 'iTotalDisplayRecords';
+ }
- return filter[0];
+ return json[old] !== undefined
+ ? json[old]
+ : json[param];
}
/**
* Filter the table using both the global filter and column based filtering
- * @param {object} oSettings dataTables settings object
- * @param {object} oSearch search information
- * @param {int} [iForce] force a research of the master array (1) or not (undefined or 0)
+ * @param {object} settings dataTables settings object
+ * @param {object} input search information
* @memberof DataTable#oApi
*/
- function _fnFilterComplete ( oSettings, oInput, iForce )
+ function _fnFilterComplete ( settings, input )
{
- var oPrevSearch = oSettings.oPreviousSearch;
- var aoPrevSearch = oSettings.aoPreSearchCols;
- var fnSaveFilter = function ( oFilter ) {
- /* Save the filtering values */
- oPrevSearch.sSearch = oFilter.sSearch;
- oPrevSearch.bRegex = oFilter.bRegex;
- oPrevSearch.bSmart = oFilter.bSmart;
- oPrevSearch.bCaseInsensitive = oFilter.bCaseInsensitive;
- oPrevSearch.return = oFilter.return;
- };
- var fnRegex = function ( o ) {
- // Backwards compatibility with the bEscapeRegex option
- return o.bEscapeRegex !== undefined ? !o.bEscapeRegex : o.bRegex;
- };
+ var columnsSearch = settings.aoPreSearchCols;
// Resolve any column types that are unknown due to addition or invalidation
// @todo As per sort - can this be moved into an event handler?
- _fnColumnTypes( oSettings );
+ _fnColumnTypes( settings );
- /* In server-side processing all filtering is done by the server, so no point hanging around here */
- if ( _fnDataSource( oSettings ) != 'ssp' )
+ // In server-side processing all filtering is done by the server, so no point hanging around here
+ if ( _fnDataSource( settings ) != 'ssp' )
{
- /* Global filter */
- _fnFilter( oSettings, oInput.sSearch, iForce, fnRegex(oInput), oInput.bSmart, oInput.bCaseInsensitive );
- fnSaveFilter( oInput );
+ // Check if any of the rows were invalidated
+ _fnFilterData( settings );
+
+ // Start from the full data set
+ settings.aiDisplay = settings.aiDisplayMaster.slice();
+
+ // Global filter first
+ _fnFilter( settings.aiDisplay, settings, input.search, input );
+
+ $.each(settings.searchFixed, function (name, term) {
+ _fnFilter(settings.aiDisplay, settings, term, {});
+ });
- /* Now do the individual column filter */
- for ( var i=0 ; i<aoPrevSearch.length ; i++ )
+ // Then individual column filters
+ for ( var i=0 ; i<columnsSearch.length ; i++ )
{
- _fnFilterColumn( oSettings, aoPrevSearch[i].sSearch, i, fnRegex(aoPrevSearch[i]),
- aoPrevSearch[i].bSmart, aoPrevSearch[i].bCaseInsensitive );
+ var col = columnsSearch[i];
+
+ _fnFilter(
+ settings.aiDisplay,
+ settings,
+ col.search,
+ col,
+ i
+ );
+
+ $.each(settings.aoColumns[i].searchFixed, function (name, term) {
+ _fnFilter(settings.aiDisplay, settings, term, {}, i);
+ });
}
- /* Custom filtering */
- _fnFilterCustom( oSettings );
- }
- else
- {
- fnSaveFilter( oInput );
+ // And finally global filtering
+ _fnFilterCustom( settings );
}
- /* Tell the draw function we have been filtering */
- oSettings.bFiltered = true;
- _fnCallbackFire( oSettings, null, 'search', [oSettings] );
+ // Tell the draw function we have been filtering
+ settings.bFiltered = true;
+
+ _fnCallbackFire( settings, null, 'search', [settings] );
}
/**
* Apply custom filtering functions
+ *
+ * This is legacy now that we have named functions, but it is widely used
+ * from 1.x, so it is not yet deprecated.
* @param {object} oSettings dataTables settings object
* @memberof DataTable#oApi
*/
@@ -4476,97 +4419,49 @@
// So the array reference doesn't break set the results into the
// existing array
displayRows.length = 0;
- $.merge( displayRows, rows );
+ displayRows.push.apply(displayRows, rows);
}
}
/**
- * Filter the table on a per-column basis
- * @param {object} oSettings dataTables settings object
- * @param {string} sInput string to filter on
- * @param {int} iColumn column to filter
- * @param {bool} bRegex treat search string as a regular expression or not
- * @param {bool} bSmart use smart filtering or not
- * @param {bool} bCaseInsensitive Do case insensitive matching or not
- * @memberof DataTable#oApi
+ * Filter the data table based on user input and draw the table
*/
- function _fnFilterColumn ( settings, searchStr, colIdx, regex, smart, caseInsensitive )
+ function _fnFilter( searchRows, settings, input, options, column )
{
- if ( searchStr === '' ) {
+ if ( input === '' ) {
return;
}
- var data;
- var out = [];
- var display = settings.aiDisplay;
- var rpSearch = _fnFilterCreateSearch( searchStr, regex, smart, caseInsensitive );
-
- for ( var i=0 ; i<display.length ; i++ ) {
- data = settings.aoData[ display[i] ]._aFilterData[ colIdx ];
-
- if ( rpSearch.test( data ) ) {
- out.push( display[i] );
- }
- }
-
- settings.aiDisplay = out;
- }
-
+ var i = 0;
+ var matched = [];
- /**
- * Filter the data table based on user input and draw the table
- * @param {object} settings dataTables settings object
- * @param {string} input string to filter on
- * @param {int} force optional - force a research of the master array (1) or not (undefined or 0)
- * @param {bool} regex treat as a regular expression or not
- * @param {bool} smart perform smart filtering or not
- * @param {bool} caseInsensitive Do case insensitive matching or not
- * @memberof DataTable#oApi
- */
- function _fnFilter( settings, input, force, regex, smart, caseInsensitive )
- {
- var rpSearch = _fnFilterCreateSearch( input, regex, smart, caseInsensitive );
- var prevSearch = settings.oPreviousSearch.sSearch;
- var displayMaster = settings.aiDisplayMaster;
- var display, invalidated, i;
- var filtered = [];
-
- // Need to take account of custom filtering functions - always filter
- if ( DataTable.ext.search.length !== 0 ) {
- force = true;
- }
+ // Search term can be a function, regex or string - if a string we apply our
+ // smart filtering regex (assuming the options require that)
+ var searchFunc = typeof input === 'function' ? input : null;
+ var rpSearch = input instanceof RegExp
+ ? input
+ : searchFunc
+ ? null
+ : _fnFilterCreateSearch( input, options );
- // Check if any of the rows were invalidated
- invalidated = _fnFilterData( settings );
+ // Then for each row, does the test pass. If not, lop the row from the array
+ for (i=0 ; i<searchRows.length ; i++) {
+ var row = settings.aoData[ searchRows[i] ];
+ var data = column === undefined
+ ? row._sFilterRow
+ : row._aFilterData[ column ];
- // If the input is blank - we just want the full data set
- if ( input.length <= 0 ) {
- settings.aiDisplay = displayMaster.slice();
- }
- else {
- // New search - start from the master array
- if ( invalidated ||
- force ||
- regex ||
- prevSearch.length > input.length ||
- input.indexOf(prevSearch) !== 0 ||
- settings.bSorted // On resort, the display master needs to be
- // re-filtered since indexes will have changed
- ) {
- settings.aiDisplay = displayMaster.slice();
+ if ( (searchFunc && searchFunc(data, row._aData, searchRows[i], column)) || (rpSearch && rpSearch.test(data)) ) {
+ matched.push(searchRows[i]);
}
+ }
- // Search the display array
- display = settings.aiDisplay;
-
- for ( i=0 ; i<display.length ; i++ ) {
- if ( rpSearch.test( settings.aoData[ display[i] ]._sFilterRow ) ) {
- filtered.push( display[i] );
- }
- }
+ // Mutate the searchRows array
+ searchRows.length = matched.length;
- settings.aiDisplay = filtered;
+ for (i=0 ; i<matched.length ; i++) {
+ searchRows[i] = matched[i];
}
}
@@ -4580,37 +4475,92 @@
* @returns {RegExp} constructed object
* @memberof DataTable#oApi
*/
- function _fnFilterCreateSearch( search, regex, smart, caseInsensitive )
+ function _fnFilterCreateSearch( search, inOpts )
{
- search = regex ?
+ var not = [];
+ var options = $.extend({}, {
+ boundary: false,
+ caseInsensitive: true,
+ exact: false,
+ regex: false,
+ smart: true
+ }, inOpts);
+
+ if (typeof search !== 'string') {
+ search = search.toString();
+ }
+
+ // Remove diacritics if normalize is set up to do so
+ search = _normalize(search);
+
+ if (options.exact) {
+ return new RegExp(
+ '^'+_fnEscapeRegex(search)+'$',
+ options.caseInsensitive ? 'i' : ''
+ );
+ }
+
+ search = options.regex ?
search :
_fnEscapeRegex( search );
- if ( smart ) {
+ if ( options.smart ) {
/* For smart filtering we want to allow the search to work regardless of
* word order. We also want double quoted text to be preserved, so word
- * order is important - a la google. So this is what we want to
- * generate:
+ * order is important - a la google. And a negative look around for
+ * finding rows which don't contain a given string.
+ *
+ * So this is the sort of thing we want to generate:
*
* ^(?=.*?\bone\b)(?=.*?\btwo three\b)(?=.*?\bfour\b).*$
*/
- var a = $.map( search.match( /["\u201C][^"\u201D]+["\u201D]|[^ ]+/g ) || [''], function ( word ) {
+ var parts = search.match( /!?["\u201C][^"\u201D]+["\u201D]|[^ ]+/g ) || [''];
+ var a = parts.map( function ( word ) {
+ var negative = false;
+ var m;
+
+ // Determine if it is a "does not include"
+ if ( word.charAt(0) === '!' ) {
+ negative = true;
+ word = word.substring(1);
+ }
+
+ // Strip the quotes from around matched phrases
if ( word.charAt(0) === '"' ) {
- var m = word.match( /^"(.*)"$/ );
+ m = word.match( /^"(.*)"$/ );
word = m ? m[1] : word;
}
else if ( word.charAt(0) === '\u201C' ) {
- var m = word.match( /^\u201C(.*)\u201D$/ );
+ // Smart quote match (iPhone users)
+ m = word.match( /^\u201C(.*)\u201D$/ );
word = m ? m[1] : word;
}
- return word.replace('"', '');
+ // For our "not" case, we need to modify the string that is
+ // allowed to match at the end of the expression.
+ if (negative) {
+ if (word.length > 1) {
+ not.push('(?!'+word+')');
+ }
+
+ word = '';
+ }
+
+ return word.replace(/"/g, '');
} );
- search = '^(?=.*?'+a.join( ')(?=.*?' )+').*$';
+ var match = not.length
+ ? not.join('')
+ : '';
+
+ var boundary = options.boundary
+ ? '\\b'
+ : '';
+
+ search = '^(?=.*?'+boundary+a.join( ')(?=.*?'+boundary )+')('+match+'.)*$';
}
- return new RegExp( search, caseInsensitive ? 'i' : '' );
+ return new RegExp( search, options.caseInsensitive ? 'i' : '' );
}
@@ -4629,12 +4579,17 @@
function _fnFilterData ( settings )
{
var columns = settings.aoColumns;
+ var data = settings.aoData;
var column;
- var i, j, ien, jen, filterData, cellData, row;
+ var j, jen, filterData, cellData, row;
var wasInvalidated = false;
- for ( i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
- row = settings.aoData[i];
+ for ( var rowIdx=0 ; rowIdx<data.length ; rowIdx++ ) {
+ if (! data[rowIdx]) {
+ continue;
+ }
+
+ row = data[rowIdx];
if ( ! row._aFilterData ) {
filterData = [];
@@ -4643,10 +4598,9 @@
column = columns[j];
if ( column.bSearchable ) {
- cellData = _fnGetCellData( settings, i, j, 'filter' );
+ cellData = _fnGetCellData( settings, rowIdx, j, 'filter' );
- // Search in DataTables 1.10 is string based. In 1.11 this
- // should be altered to also allow strict type checking.
+ // Search in DataTables is string based
if ( cellData === null ) {
cellData = '';
}
@@ -4662,7 +4616,7 @@
// If it looks like there is an HTML entity in the string,
// attempt to decode it so sorting works as expected. Note that
// we could use a single line of jQuery to do this, but the DOM
- // method used here is much faster http://jsperf.com/html-decode
+ // method used here is much faster https://jsperf.com/html-decode
if ( cellData.indexOf && cellData.indexOf('&') !== -1 ) {
__filter_div.innerHTML = cellData;
cellData = __filter_div_textContent ?
@@ -4688,152 +4642,13 @@
/**
- * Convert from the internal Hungarian notation to camelCase for external
- * interaction
- * @param {object} obj Object to convert
- * @returns {object} Inverted object
- * @memberof DataTable#oApi
- */
- function _fnSearchToCamel ( obj )
- {
- return {
- search: obj.sSearch,
- smart: obj.bSmart,
- regex: obj.bRegex,
- caseInsensitive: obj.bCaseInsensitive
- };
- }
-
-
-
- /**
- * Convert from camelCase notation to the internal Hungarian. We could use the
- * Hungarian convert function here, but this is cleaner
- * @param {object} obj Object to convert
- * @returns {object} Inverted object
- * @memberof DataTable#oApi
- */
- function _fnSearchToHung ( obj )
- {
- return {
- sSearch: obj.search,
- bSmart: obj.smart,
- bRegex: obj.regex,
- bCaseInsensitive: obj.caseInsensitive
- };
- }
-
- /**
- * Generate the node required for the info display
- * @param {object} oSettings dataTables settings object
- * @returns {node} Information element
- * @memberof DataTable#oApi
- */
- function _fnFeatureHtmlInfo ( settings )
- {
- var
- tid = settings.sTableId,
- nodes = settings.aanFeatures.i,
- n = $('<div/>', {
- 'class': settings.oClasses.sInfo,
- 'id': ! nodes ? tid+'_info' : null
- } );
-
- if ( ! nodes ) {
- // Update display on each draw
- settings.aoDrawCallback.push( {
- "fn": _fnUpdateInfo,
- "sName": "information"
- } );
-
- n
- .attr( 'role', 'status' )
- .attr( 'aria-live', 'polite' );
-
- // Table is described by our info div
- $(settings.nTable).attr( 'aria-describedby', tid+'_info' );
- }
-
- return n[0];
- }
-
-
- /**
- * Update the information elements in the display
- * @param {object} settings dataTables settings object
- * @memberof DataTable#oApi
- */
- function _fnUpdateInfo ( settings )
- {
- /* Show information about the table */
- var nodes = settings.aanFeatures.i;
- if ( nodes.length === 0 ) {
- return;
- }
-
- var
- lang = settings.oLanguage,
- start = settings._iDisplayStart+1,
- end = settings.fnDisplayEnd(),
- max = settings.fnRecordsTotal(),
- total = settings.fnRecordsDisplay(),
- out = total ?
- lang.sInfo :
- lang.sInfoEmpty;
-
- if ( total !== max ) {
- /* Record set after filtering */
- out += ' ' + lang.sInfoFiltered;
- }
-
- // Convert the macros
- out += lang.sInfoPostFix;
- out = _fnInfoMacros( settings, out );
-
- var callback = lang.fnInfoCallback;
- if ( callback !== null ) {
- out = callback.call( settings.oInstance,
- settings, start, end, max, total, out
- );
- }
-
- $(nodes).html( out );
- }
-
-
- function _fnInfoMacros ( settings, str )
- {
- // When infinite scrolling, we are always starting at 1. _iDisplayStart is used only
- // internally
- var
- formatter = settings.fnFormatNumber,
- start = settings._iDisplayStart+1,
- len = settings._iDisplayLength,
- vis = settings.fnRecordsDisplay(),
- all = len === -1;
-
- return str.
- replace(/_START_/g, formatter.call( settings, start ) ).
- replace(/_END_/g, formatter.call( settings, settings.fnDisplayEnd() ) ).
- replace(/_MAX_/g, formatter.call( settings, settings.fnRecordsTotal() ) ).
- replace(/_TOTAL_/g, formatter.call( settings, vis ) ).
- replace(/_PAGE_/g, formatter.call( settings, all ? 1 : Math.ceil( start / len ) ) ).
- replace(/_PAGES_/g, formatter.call( settings, all ? 1 : Math.ceil( vis / len ) ) );
- }
-
-
-
- /**
* Draw the table for the first time, adding all required features
* @param {object} settings dataTables settings object
* @memberof DataTable#oApi
*/
function _fnInitialise ( settings )
{
- var i, iLen, iAjaxStart=settings.iInitDisplayStart;
- var columns = settings.aoColumns, column;
- var features = settings.oFeatures;
- var deferLoading = settings.bDeferLoading; // value modified by the draw
+ var i, iAjaxStart=settings.iInitDisplayStart;
/* Ensure that the table data is fully initialised */
if ( ! settings.bInitialised ) {
@@ -4841,31 +4656,22 @@
return;
}
- /* Show the display HTML options */
- _fnAddOptionsHtml( settings );
-
/* Build and draw the header / footer for the table */
- _fnBuildHead( settings );
+ _fnBuildHead( settings, 'header' );
+ _fnBuildHead( settings, 'footer' );
_fnDrawHead( settings, settings.aoHeader );
_fnDrawHead( settings, settings.aoFooter );
- /* Okay to show that something is going on now */
- _fnProcessingDisplay( settings, true );
-
- /* Calculate sizes for columns */
- if ( features.bAutoWidth ) {
- _fnCalculateColumnWidths( settings );
- }
+ // Enable features
+ _fnAddOptionsHtml( settings );
+ _fnSortInit( settings );
- for ( i=0, iLen=columns.length ; i<iLen ; i++ ) {
- column = columns[i];
+ _colGroup( settings );
- if ( column.sWidth ) {
- column.nTh.style.width = _fnStringToCss( column.sWidth );
- }
- }
+ /* Okay to show that something is going on now */
+ _fnProcessingDisplay( settings, true );
- _fnCallbackFire( settings, null, 'preInit', [settings] );
+ _fnCallbackFire( settings, null, 'preInit', [settings], true );
// If there is default sorting required - let's do it. The sort function
// will do the drawing for us. Otherwise we draw the table regardless of the
@@ -4873,12 +4679,13 @@
// data (show 'loading' message possibly)
_fnReDraw( settings );
+ var dataSrc = _fnDataSource( settings );
+
// Server-side processing init complete is done by _fnAjaxUpdateDraw
- var dataSrc = _fnDataSource( settings );
- if ( dataSrc != 'ssp' || deferLoading ) {
+ if ( dataSrc != 'ssp' ) {
// if there is an ajax source load the data
if ( dataSrc == 'ajax' ) {
- _fnBuildAjax( settings, [], function(json) {
+ _fnBuildAjax( settings, {}, function(json) {
var aData = _fnAjaxDataSrc( settings, json );
// Got the data - add it to the table
@@ -4892,14 +4699,13 @@
settings.iInitDisplayStart = iAjaxStart;
_fnReDraw( settings );
-
_fnProcessingDisplay( settings, false );
- _fnInitComplete( settings, json );
+ _fnInitComplete( settings );
}, settings );
}
else {
- _fnProcessingDisplay( settings, false );
_fnInitComplete( settings );
+ _fnProcessingDisplay( settings, false );
}
}
}
@@ -4907,26 +4713,27 @@
/**
* Draw the table for the first time, adding all required features
- * @param {object} oSettings dataTables settings object
- * @param {object} [json] JSON from the server that completed the table, if using Ajax source
- * with client-side processing (optional)
+ * @param {object} settings dataTables settings object
* @memberof DataTable#oApi
*/
- function _fnInitComplete ( settings, json )
+ function _fnInitComplete ( settings )
{
+ if (settings._bInitComplete) {
+ return;
+ }
+
+ var args = [settings, settings.json];
+
settings._bInitComplete = true;
- // When data was added after the initialisation (data or Ajax) we need to
- // calculate the column sizing
- if ( json || settings.oInit.aaData ) {
- _fnAdjustColumnSizing( settings );
- }
+ // Table is fully set up and we have data, so calculate the
+ // column widths
+ _fnAdjustColumnSizing( settings );
- _fnCallbackFire( settings, null, 'plugin-init', [settings, json] );
- _fnCallbackFire( settings, 'aoInitComplete', 'init', [settings, json] );
+ _fnCallbackFire( settings, null, 'plugin-init', args, true );
+ _fnCallbackFire( settings, 'aoInitComplete', 'init', args, true );
}
-
function _fnLengthChange ( settings, val )
{
var len = parseInt( val, 10 );
@@ -4938,131 +4745,6 @@
_fnCallbackFire( settings, null, 'length', [settings, len] );
}
-
- /**
- * Generate the node required for user display length changing
- * @param {object} settings dataTables settings object
- * @returns {node} Display length feature node
- * @memberof DataTable#oApi
- */
- function _fnFeatureHtmlLength ( settings )
- {
- var
- classes = settings.oClasses,
- tableId = settings.sTableId,
- menu = settings.aLengthMenu,
- d2 = Array.isArray( menu[0] ),
- lengths = d2 ? menu[0] : menu,
- language = d2 ? menu[1] : menu;
-
- var select = $('<select/>', {
- 'name': tableId+'_length',
- 'aria-controls': tableId,
- 'class': classes.sLengthSelect
- } );
-
- for ( var i=0, ien=lengths.length ; i<ien ; i++ ) {
- select[0][ i ] = new Option(
- typeof language[i] === 'number' ?
- settings.fnFormatNumber( language[i] ) :
- language[i],
- lengths[i]
- );
- }
-
- var div = $('<div><label/></div>').addClass( classes.sLength );
- if ( ! settings.aanFeatures.l ) {
- div[0].id = tableId+'_length';
- }
-
- div.children().append(
- settings.oLanguage.sLengthMenu.replace( '_MENU_', select[0].outerHTML )
- );
-
- // Can't use `select` variable as user might provide their own and the
- // reference is broken by the use of outerHTML
- $('select', div)
- .val( settings._iDisplayLength )
- .on( 'change.DT', function(e) {
- _fnLengthChange( settings, $(this).val() );
- _fnDraw( settings );
- } );
-
- // Update node value whenever anything changes the table's length
- $(settings.nTable).on( 'length.dt.DT', function (e, s, len) {
- if ( settings === s ) {
- $('select', div).val( len );
- }
- } );
-
- return div[0];
- }
-
-
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * Note that most of the paging logic is done in
- * DataTable.ext.pager
- */
-
- /**
- * Generate the node required for default pagination
- * @param {object} oSettings dataTables settings object
- * @returns {node} Pagination feature node
- * @memberof DataTable#oApi
- */
- function _fnFeatureHtmlPaginate ( settings )
- {
- var
- type = settings.sPaginationType,
- plugin = DataTable.ext.pager[ type ],
- modern = typeof plugin === 'function',
- redraw = function( settings ) {
- _fnDraw( settings );
- },
- node = $('<div/>').addClass( settings.oClasses.sPaging + type )[0],
- features = settings.aanFeatures;
-
- if ( ! modern ) {
- plugin.fnInit( settings, node, redraw );
- }
-
- /* Add a draw callback for the pagination on first instance, to update the paging display */
- if ( ! features.p )
- {
- node.id = settings.sTableId+'_paginate';
-
- settings.aoDrawCallback.push( {
- "fn": function( settings ) {
- if ( modern ) {
- var
- start = settings._iDisplayStart,
- len = settings._iDisplayLength,
- visRecords = settings.fnRecordsDisplay(),
- all = len === -1,
- page = all ? 0 : Math.ceil( start / len ),
- pages = all ? 1 : Math.ceil( visRecords / len ),
- buttons = plugin(page, pages),
- i, ien;
-
- for ( i=0, ien=features.p.length ; i<ien ; i++ ) {
- _fnRenderer( settings, 'pageButton' )(
- settings, features.p[i], i, buttons, page, pages
- );
- }
- }
- else {
- plugin.fnUpdate( settings, redraw );
- }
- },
- "sName": "pagination"
- } );
- }
-
- return node;
- }
-
-
/**
* Alter the display settings to change the page
* @param {object} settings DataTables settings object
@@ -5104,7 +4786,7 @@
if ( start < 0 )
{
- start = 0;
+ start = 0;
}
}
else if ( action == "next" )
@@ -5118,6 +4800,10 @@
{
start = Math.floor( (records-1) / len) * len;
}
+ else if ( action === 'ellipsis' )
+ {
+ return;
+ }
else
{
_fnLog( settings, 0, "Unknown paging action: "+action, 5 );
@@ -5126,57 +4812,58 @@
var changed = settings._iDisplayStart !== start;
settings._iDisplayStart = start;
- if ( changed ) {
- _fnCallbackFire( settings, null, 'page', [settings] );
+ _fnCallbackFire( settings, null, changed ? 'page' : 'page-nc', [settings] );
- if ( redraw ) {
- _fnDraw( settings );
- }
- }
- else {
- // No change event - paging was called, but no change
- _fnCallbackFire( settings, null, 'page-nc', [settings] );
+ if ( changed && redraw ) {
+ _fnDraw( settings );
}
return changed;
}
-
/**
* Generate the node required for the processing node
- * @param {object} settings dataTables settings object
- * @returns {node} Processing element
- * @memberof DataTable#oApi
+ * @param {object} settings DataTables settings object
*/
- function _fnFeatureHtmlProcessing ( settings )
+ function _processingHtml ( settings )
{
- return $('<div/>', {
- 'id': ! settings.aanFeatures.r ? settings.sTableId+'_processing' : null,
- 'class': settings.oClasses.sProcessing,
- 'role': 'status'
- } )
- .html( settings.oLanguage.sProcessing )
- .append('<div><div></div><div></div><div></div><div></div></div>')
- .insertBefore( settings.nTable )[0];
+ var table = settings.nTable;
+ var scrolling = settings.oScroll.sX !== '' || settings.oScroll.sY !== '';
+
+ if ( settings.oFeatures.bProcessing ) {
+ var n = $('<div/>', {
+ 'id': settings.sTableId + '_processing',
+ 'class': settings.oClasses.processing.container,
+ 'role': 'status'
+ } )
+ .html( settings.oLanguage.sProcessing )
+ .append('<div><div></div><div></div><div></div><div></div></div>');
+
+ // Different positioning depending on if scrolling is enabled or not
+ if (scrolling) {
+ n.prependTo( $('div.dt-scroll', settings.nTableWrapper) );
+ }
+ else {
+ n.insertBefore( table );
+ }
+
+ $(table).on( 'processing.dt.DT', function (e, s, show) {
+ n.css( 'display', show ? 'block' : 'none' );
+ } );
+ }
}
/**
* Display or hide the processing indicator
- * @param {object} settings dataTables settings object
+ * @param {object} settings DataTables settings object
* @param {bool} show Show the processing indicator (true) or not (false)
- * @memberof DataTable#oApi
*/
function _fnProcessingDisplay ( settings, show )
{
- if ( settings.oFeatures.bProcessing ) {
- $(settings.aanFeatures.r).css( 'display', show ? 'block' : 'none' );
- }
-
_fnCallbackFire( settings, null, 'processing', [settings, show] );
}
-
/**
* Add any control elements for the table - specifically scrolling
* @param {object} settings dataTables settings object
@@ -5196,9 +4883,9 @@
var scrollX = scroll.sX;
var scrollY = scroll.sY;
- var classes = settings.oClasses;
- var caption = table.children('caption');
- var captionSide = caption.length ? caption[0]._captionSide : null;
+ var classes = settings.oClasses.scrolling;
+ var caption = settings.captionNode;
+ var captionSide = caption ? caption._captionSide : null;
var headerClone = $( table[0].cloneNode(false) );
var footerClone = $( table[0].cloneNode(false) );
var footer = table.children('tfoot');
@@ -5227,9 +4914,9 @@
* table - scroll foot table
* tfoot - tfoot
*/
- var scroller = $( _div, { 'class': classes.sScrollWrapper } )
+ var scroller = $( _div, { 'class': classes.container } )
.append(
- $(_div, { 'class': classes.sScrollHead } )
+ $(_div, { 'class': classes.header.self } )
.css( {
overflow: 'hidden',
position: 'relative',
@@ -5237,7 +4924,7 @@
width: scrollX ? size(scrollX) : '100%'
} )
.append(
- $(_div, { 'class': classes.sScrollHeadInner } )
+ $(_div, { 'class': classes.header.inner } )
.css( {
'box-sizing': 'content-box',
width: scroll.sXInner || '100%'
@@ -5254,7 +4941,7 @@
)
)
.append(
- $(_div, { 'class': classes.sScrollBody } )
+ $(_div, { 'class': classes.body } )
.css( {
position: 'relative',
overflow: 'auto',
@@ -5265,14 +4952,14 @@
if ( footer ) {
scroller.append(
- $(_div, { 'class': classes.sScrollFoot } )
+ $(_div, { 'class': classes.footer.self } )
.css( {
overflow: 'hidden',
border: 0,
width: scrollX ? size(scrollX) : '100%'
} )
.append(
- $(_div, { 'class': classes.sScrollFootInner } )
+ $(_div, { 'class': classes.footer.inner } )
.append(
footerClone
.removeAttr('id')
@@ -5292,17 +4979,26 @@
var scrollFoot = footer ? children[2] : null;
// When the body is scrolled, then we also want to scroll the headers
- if ( scrollX ) {
- $(scrollBody).on( 'scroll.DT', function (e) {
- var scrollLeft = this.scrollLeft;
+ $(scrollBody).on( 'scroll.DT', function () {
+ var scrollLeft = this.scrollLeft;
- scrollHead.scrollLeft = scrollLeft;
+ scrollHead.scrollLeft = scrollLeft;
- if ( footer ) {
- scrollFoot.scrollLeft = scrollLeft;
- }
- } );
- }
+ if ( footer ) {
+ scrollFoot.scrollLeft = scrollLeft;
+ }
+ } );
+
+ // When focus is put on the header cells, we might need to scroll the body
+ $('th, td', scrollHead).on('focus', function () {
+ var scrollLeft = scrollHead.scrollLeft;
+
+ scrollBody.scrollLeft = scrollLeft;
+
+ if ( footer ) {
+ scrollBody.scrollLeft = scrollLeft;
+ }
+ });
$(scrollBody).css('max-height', scrollY);
if (! scroll.bCollapse) {
@@ -5314,10 +5010,7 @@
settings.nScrollFoot = scrollFoot;
// On redraw - align columns
- settings.aoDrawCallback.push( {
- "fn": _fnScrollDraw,
- "sName": "scrolling"
- } );
+ settings.aoDrawCallback.push(_fnScrollDraw);
return scroller[0];
}
@@ -5331,8 +5024,8 @@
* Welcome to the most horrible function DataTables. The process that this
* function follows is basically:
* 1. Re-create the table inside the scrolling div
- * 2. Take live measurements from the DOM
- * 3. Apply the measurements to align the columns
+ * 2. Correct colgroup > col values if needed
+ * 3. Copy colgroup > col over to header and footer
* 4. Clean up
*
* @param {object} settings dataTables settings object
@@ -5344,43 +5037,20 @@
// to try and keep the minimised size as small as possible
var
scroll = settings.oScroll,
- scrollX = scroll.sX,
- scrollXInner = scroll.sXInner,
- scrollY = scroll.sY,
barWidth = scroll.iBarWidth,
divHeader = $(settings.nScrollHead),
- divHeaderStyle = divHeader[0].style,
divHeaderInner = divHeader.children('div'),
- divHeaderInnerStyle = divHeaderInner[0].style,
divHeaderTable = divHeaderInner.children('table'),
divBodyEl = settings.nScrollBody,
divBody = $(divBodyEl),
- divBodyStyle = divBodyEl.style,
divFooter = $(settings.nScrollFoot),
divFooterInner = divFooter.children('div'),
divFooterTable = divFooterInner.children('table'),
header = $(settings.nTHead),
table = $(settings.nTable),
- tableEl = table[0],
- tableStyle = tableEl.style,
- footer = settings.nTFoot ? $(settings.nTFoot) : null,
+ footer = settings.nTFoot && $('th, td', settings.nTFoot).length ? $(settings.nTFoot) : null,
browser = settings.oBrowser,
- ie67 = browser.bScrollOversize,
- dtHeaderCells = _pluck( settings.aoColumns, 'nTh' ),
- headerTrgEls, footerTrgEls,
- headerSrcEls, footerSrcEls,
- headerCopy, footerCopy,
- headerWidths=[], footerWidths=[],
- headerContent=[], footerContent=[],
- idx, correction, sanityWidth,
- zeroOut = function(nSizer) {
- var style = nSizer.style;
- style.paddingTop = "0";
- style.paddingBottom = "0";
- style.borderTopWidth = "0";
- style.borderBottomWidth = "0";
- style.height = 0;
- };
+ headerCopy, footerCopy;
// If the scrollbar visibility has changed from the last draw, we need to
// adjust the column sizes as the table width will have changed to account
@@ -5396,214 +5066,102 @@
settings.scrollBarVis = scrollBarVis;
}
- /*
- * 1. Re-create the table inside the scrolling div
- */
-
+ // 1. Re-create the table inside the scrolling div
// Remove the old minimised thead and tfoot elements in the inner table
table.children('thead, tfoot').remove();
- if ( footer ) {
- footerCopy = footer.clone().prependTo( table );
- footerTrgEls = footer.find('tr'); // the original tfoot is in its own table and must be sized
- footerSrcEls = footerCopy.find('tr');
- footerCopy.find('[id]').removeAttr('id');
- }
-
// Clone the current header and footer elements and then place it into the inner table
headerCopy = header.clone().prependTo( table );
- headerTrgEls = header.find('tr'); // original header is in its own table
- headerSrcEls = headerCopy.find('tr');
headerCopy.find('th, td').removeAttr('tabindex');
headerCopy.find('[id]').removeAttr('id');
-
- /*
- * 2. Take live measurements from the DOM - do not alter the DOM itself!
- */
-
- // Remove old sizing and apply the calculated column widths
- // Get the unique column headers in the newly created (cloned) header. We want to apply the
- // calculated sizes to this header
- if ( ! scrollX )
- {
- divBodyStyle.width = '100%';
- divHeader[0].style.width = '100%';
- }
-
- $.each( _fnGetUniqueThs( settings, headerCopy ), function ( i, el ) {
- idx = _fnVisibleToColumnIndex( settings, i );
- el.style.width = settings.aoColumns[idx].sWidth;
- } );
-
if ( footer ) {
- _fnApplyToChildren( function(n) {
- n.style.width = "";
- }, footerSrcEls );
- }
-
- // Size the table as a whole
- sanityWidth = table.outerWidth();
- if ( scrollX === "" ) {
- // No x scrolling
- tableStyle.width = "100%";
-
- // IE7 will make the width of the table when 100% include the scrollbar
- // - which is shouldn't. When there is a scrollbar we need to take this
- // into account.
- if ( ie67 && (table.find('tbody').height() > divBodyEl.offsetHeight ||
- divBody.css('overflow-y') == "scroll")
- ) {
- tableStyle.width = _fnStringToCss( table.outerWidth() - barWidth);
- }
-
- // Recalculate the sanity width
- sanityWidth = table.outerWidth();
- }
- else if ( scrollXInner !== "" ) {
- // legacy x scroll inner has been given - use it
- tableStyle.width = _fnStringToCss(scrollXInner);
-
- // Recalculate the sanity width
- sanityWidth = table.outerWidth();
+ footerCopy = footer.clone().prependTo( table );
+ footerCopy.find('[id]').removeAttr('id');
}
- // Hidden header should have zero height, so remove padding and borders. Then
- // set the width based on the real headers
-
- // Apply all styles in one pass
- _fnApplyToChildren( zeroOut, headerSrcEls );
-
- // Read all widths in next pass
- _fnApplyToChildren( function(nSizer) {
- var style = window.getComputedStyle ?
- window.getComputedStyle(nSizer).width :
- _fnStringToCss( $(nSizer).width() );
-
- headerContent.push( nSizer.innerHTML );
- headerWidths.push( style );
- }, headerSrcEls );
+ // 2. Correct colgroup > col values if needed
+ // It is possible that the cell sizes are smaller than the content, so we need to
+ // correct colgroup>col for such cases. This can happen if the auto width detection
+ // uses a cell which has a longer string, but isn't the widest! For example
+ // "Chief Executive Officer (CEO)" is the longest string in the demo, but
+ // "Systems Administrator" is actually the widest string since it doesn't collapse.
+ // Note the use of translating into a column index to get the `col` element. This
+ // is because of Responsive which might remove `col` elements, knocking the alignment
+ // of the indexes out.
+ if (settings.aiDisplay.length) {
+ // Get the column sizes from the first row in the table
+ var colSizes = table.children('tbody').eq(0).children('tr').eq(0).children('th, td').map(function (vis) {
+ return {
+ idx: _fnVisibleToColumnIndex(settings, vis),
+ width: $(this).outerWidth()
+ }
+ });
- // Apply all widths in final pass
- _fnApplyToChildren( function(nToSize, i) {
- nToSize.style.width = headerWidths[i];
- }, headerTrgEls );
+ // Check against what the colgroup > col is set to and correct if needed
+ for (var i=0 ; i<colSizes.length ; i++) {
+ var colEl = settings.aoColumns[ colSizes[i].idx ].colEl[0];
+ var colWidth = colEl.style.width.replace('px', '');
- $(headerSrcEls).css('height', 0);
+ if (colWidth !== colSizes[i].width) {
+ colEl.style.width = colSizes[i].width + 'px';
+ }
+ }
+ }
- /* Same again with the footer if we have one */
- if ( footer )
- {
- _fnApplyToChildren( zeroOut, footerSrcEls );
+ // 3. Copy the colgroup over to the header and footer
+ divHeaderTable
+ .find('colgroup')
+ .remove();
- _fnApplyToChildren( function(nSizer) {
- footerContent.push( nSizer.innerHTML );
- footerWidths.push( _fnStringToCss( $(nSizer).css('width') ) );
- }, footerSrcEls );
+ divHeaderTable.append(settings.colgroup.clone());
- _fnApplyToChildren( function(nToSize, i) {
- nToSize.style.width = footerWidths[i];
- }, footerTrgEls );
+ if ( footer ) {
+ divFooterTable
+ .find('colgroup')
+ .remove();
- $(footerSrcEls).height(0);
+ divFooterTable.append(settings.colgroup.clone());
}
-
- /*
- * 3. Apply the measurements
- */
-
// "Hide" the header and footer that we used for the sizing. We need to keep
// the content of the cell so that the width applied to the header and body
- // both match, but we want to hide it completely. We want to also fix their
- // width to what they currently are
- _fnApplyToChildren( function(nSizer, i) {
- nSizer.innerHTML = '<div class="dataTables_sizing">'+headerContent[i]+'</div>';
- nSizer.childNodes[0].style.height = "0";
- nSizer.childNodes[0].style.overflow = "hidden";
- nSizer.style.width = headerWidths[i];
- }, headerSrcEls );
-
- if ( footer )
- {
- _fnApplyToChildren( function(nSizer, i) {
- nSizer.innerHTML = '<div class="dataTables_sizing">'+footerContent[i]+'</div>';
- nSizer.childNodes[0].style.height = "0";
- nSizer.childNodes[0].style.overflow = "hidden";
- nSizer.style.width = footerWidths[i];
- }, footerSrcEls );
- }
-
- // Sanity check that the table is of a sensible width. If not then we are going to get
- // misalignment - try to prevent this by not allowing the table to shrink below its min width
- if ( Math.round(table.outerWidth()) < Math.round(sanityWidth) )
- {
- // The min width depends upon if we have a vertical scrollbar visible or not */
- correction = ((divBodyEl.scrollHeight > divBodyEl.offsetHeight ||
- divBody.css('overflow-y') == "scroll")) ?
- sanityWidth+barWidth :
- sanityWidth;
-
- // IE6/7 are a law unto themselves...
- if ( ie67 && (divBodyEl.scrollHeight >
- divBodyEl.offsetHeight || divBody.css('overflow-y') == "scroll")
- ) {
- tableStyle.width = _fnStringToCss( correction-barWidth );
- }
-
- // And give the user a warning that we've stopped the table getting too small
- if ( scrollX === "" || scrollXInner !== "" ) {
- _fnLog( settings, 1, 'Possible column misalignment', 6 );
- }
- }
- else
- {
- correction = '100%';
- }
-
- // Apply to the container elements
- divBodyStyle.width = _fnStringToCss( correction );
- divHeaderStyle.width = _fnStringToCss( correction );
+ // both match, but we want to hide it completely.
+ $('th, td', headerCopy).each(function () {
+ $(this.childNodes).wrapAll('<div class="dt-scroll-sizing">');
+ });
if ( footer ) {
- settings.nScrollFoot.style.width = _fnStringToCss( correction );
- }
-
-
- /*
- * 4. Clean up
- */
- if ( ! scrollY ) {
- /* IE7< puts a vertical scrollbar in place (when it shouldn't be) due to subtracting
- * the scrollbar height from the visible display, rather than adding it on. We need to
- * set the height in order to sort this. Don't want to do it in any other browsers.
- */
- if ( ie67 ) {
- divBodyStyle.height = _fnStringToCss( tableEl.offsetHeight+barWidth );
- }
+ $('th, td', footerCopy).each(function () {
+ $(this.childNodes).wrapAll('<div class="dt-scroll-sizing">');
+ });
}
- /* Finally set the width's of the header and footer tables */
- var iOuterWidth = table.outerWidth();
- divHeaderTable[0].style.width = _fnStringToCss( iOuterWidth );
- divHeaderInnerStyle.width = _fnStringToCss( iOuterWidth );
-
+ // 4. Clean up
// Figure out if there are scrollbar present - if so then we need a the header and footer to
// provide a bit more space to allow "overflow" scrolling (i.e. past the scrollbar)
- var bScrolling = table.height() > divBodyEl.clientHeight || divBody.css('overflow-y') == "scroll";
- var padding = 'padding' + (browser.bScrollbarLeft ? 'Left' : 'Right' );
- divHeaderInnerStyle[ padding ] = bScrolling ? barWidth+"px" : "0px";
+ var isScrolling = Math.floor(table.height()) > divBodyEl.clientHeight || divBody.css('overflow-y') == "scroll";
+ var paddingSide = 'padding' + (browser.bScrollbarLeft ? 'Left' : 'Right' );
+
+ // Set the width's of the header and footer tables
+ var outerWidth = table.outerWidth();
+
+ divHeaderTable.css('width', _fnStringToCss( outerWidth ));
+ divHeaderInner
+ .css('width', _fnStringToCss( outerWidth ))
+ .css(paddingSide, isScrolling ? barWidth+"px" : "0px");
if ( footer ) {
- divFooterTable[0].style.width = _fnStringToCss( iOuterWidth );
- divFooterInner[0].style.width = _fnStringToCss( iOuterWidth );
- divFooterInner[0].style[padding] = bScrolling ? barWidth+"px" : "0px";
+ divFooterTable.css('width', _fnStringToCss( outerWidth ));
+ divFooterInner
+ .css('width', _fnStringToCss( outerWidth ))
+ .css(paddingSide, isScrolling ? barWidth+"px" : "0px");
}
// Correct DOM ordering for colgroup - comes before the thead
- table.children('colgroup').insertBefore( table.children('thead') );
+ table.children('colgroup').prependTo(table);
- /* Adjust the position of the header in case we loose the y-scrollbar */
+ // Adjust the position of the header in case we loose the y-scrollbar
divBody.trigger('scroll');
// If sorting or filtering has occurred, jump the scrolling back to the top
@@ -5613,148 +5171,81 @@
}
}
-
-
/**
- * Apply a given function to the display child nodes of an element array (typically
- * TD children of TR rows
- * @param {function} fn Method to apply to the objects
- * @param array {nodes} an1 List of elements to look through for display children
- * @param array {nodes} an2 Another list (identical structure to the first) - optional
+ * Calculate the width of columns for the table
+ * @param {object} settings dataTables settings object
* @memberof DataTable#oApi
*/
- function _fnApplyToChildren( fn, an1, an2 )
+ function _fnCalculateColumnWidths ( settings )
{
- var index=0, i=0, iLen=an1.length;
- var nNode1, nNode2;
-
- while ( i < iLen ) {
- nNode1 = an1[i].firstChild;
- nNode2 = an2 ? an2[i].firstChild : null;
-
- while ( nNode1 ) {
- if ( nNode1.nodeType === 1 ) {
- if ( an2 ) {
- fn( nNode1, nNode2, index );
- }
- else {
- fn( nNode1, index );
- }
-
- index++;
- }
-
- nNode1 = nNode1.nextSibling;
- nNode2 = an2 ? nNode2.nextSibling : null;
- }
-
- i++;
+ // Not interested in doing column width calculation if auto-width is disabled
+ if (! settings.oFeatures.bAutoWidth) {
+ return;
}
- }
-
-
- var __re_html_remove = /<.*?>/g;
-
-
- /**
- * Calculate the width of columns for the table
- * @param {object} oSettings dataTables settings object
- * @memberof DataTable#oApi
- */
- function _fnCalculateColumnWidths ( oSettings )
- {
var
- table = oSettings.nTable,
- columns = oSettings.aoColumns,
- scroll = oSettings.oScroll,
+ table = settings.nTable,
+ columns = settings.aoColumns,
+ scroll = settings.oScroll,
scrollY = scroll.sY,
scrollX = scroll.sX,
scrollXInner = scroll.sXInner,
- columnCount = columns.length,
- visibleColumns = _fnGetColumns( oSettings, 'bVisible' ),
- headerCells = $('th', oSettings.nTHead),
+ visibleColumns = _fnGetColumns( settings, 'bVisible' ),
tableWidthAttr = table.getAttribute('width'), // from DOM element
tableContainer = table.parentNode,
- userInputs = false,
- i, column, columnIdx, width, outerWidth,
- browser = oSettings.oBrowser,
- ie67 = browser.bScrollOversize;
+ i, column, columnIdx;
var styleWidth = table.style.width;
if ( styleWidth && styleWidth.indexOf('%') !== -1 ) {
tableWidthAttr = styleWidth;
}
- /* Convert any user input sizes into pixel sizes */
- for ( i=0 ; i<visibleColumns.length ; i++ ) {
- column = columns[ visibleColumns[i] ];
+ // Let plug-ins know that we are doing a recalc, in case they have changed any of the
+ // visible columns their own way (e.g. Responsive uses display:none).
+ _fnCallbackFire(
+ settings,
+ null,
+ 'column-calc',
+ {visible: visibleColumns},
+ false
+ );
- if ( column.sWidth !== null ) {
- column.sWidth = _fnConvertToWidth( column.sWidthOrig, tableContainer );
+ // Construct a single row, worst case, table with the widest
+ // node in the data, assign any user defined widths, then insert it into
+ // the DOM and allow the browser to do all the hard work of calculating
+ // table widths
+ var tmpTable = $(table.cloneNode())
+ .css( 'visibility', 'hidden' )
+ .removeAttr( 'id' );
- userInputs = true;
- }
- }
+ // Clean up the table body
+ tmpTable.append('<tbody>')
+ var tr = $('<tr/>').appendTo( tmpTable.find('tbody') );
- /* If the number of columns in the DOM equals the number that we have to
- * process in DataTables, then we can use the offsets that are created by
- * the web- browser. No custom sizes can be set in order for this to happen,
- * nor scrolling used
- */
- if ( ie67 || ! userInputs && ! scrollX && ! scrollY &&
- columnCount == _fnVisbleColumns( oSettings ) &&
- columnCount == headerCells.length
- ) {
- for ( i=0 ; i<columnCount ; i++ ) {
- var colIdx = _fnVisibleToColumnIndex( oSettings, i );
+ // Clone the table header and footer - we can't use the header / footer
+ // from the cloned table, since if scrolling is active, the table's
+ // real header and footer are contained in different table tags
+ tmpTable
+ .append( $(settings.nTHead).clone() )
+ .append( $(settings.nTFoot).clone() );
- if ( colIdx !== null ) {
- columns[ colIdx ].sWidth = _fnStringToCss( headerCells.eq(i).width() );
- }
- }
- }
- else
- {
- // Otherwise construct a single row, worst case, table with the widest
- // node in the data, assign any user defined widths, then insert it into
- // the DOM and allow the browser to do all the hard work of calculating
- // table widths
- var tmpTable = $(table).clone() // don't use cloneNode - IE8 will remove events on the main table
- .css( 'visibility', 'hidden' )
- .removeAttr( 'id' );
-
- // Clean up the table body
- tmpTable.find('tbody tr').remove();
- var tr = $('<tr/>').appendTo( tmpTable.find('tbody') );
-
- // Clone the table header and footer - we can't use the header / footer
- // from the cloned table, since if scrolling is active, the table's
- // real header and footer are contained in different table tags
- tmpTable.find('thead, tfoot').remove();
- tmpTable
- .append( $(oSettings.nTHead).clone() )
- .append( $(oSettings.nTFoot).clone() );
-
- // Remove any assigned widths from the footer (from scrolling)
- tmpTable.find('tfoot th, tfoot td').css('width', '');
-
- // Apply custom sizing to the cloned header
- headerCells = _fnGetUniqueThs( oSettings, tmpTable.find('thead')[0] );
-
- for ( i=0 ; i<visibleColumns.length ; i++ ) {
- column = columns[ visibleColumns[i] ];
-
- headerCells[i].style.width = column.sWidthOrig !== null && column.sWidthOrig !== '' ?
- _fnStringToCss( column.sWidthOrig ) :
- '';
+ // Remove any assigned widths from the footer (from scrolling)
+ tmpTable.find('tfoot th, tfoot td').css('width', '');
+
+ // Apply custom sizing to the cloned header
+ tmpTable.find('thead th, thead td').each( function () {
+ // Get the `width` from the header layout
+ var width = _fnColumnsSumWidth( settings, this, true, false );
+
+ if ( width ) {
+ this.style.width = width;
// For scrollX we need to force the column width otherwise the
// browser will collapse it. If this width is smaller than the
// width the column requires, then it will have no effect
- if ( column.sWidthOrig && scrollX ) {
- $( headerCells[i] ).append( $('<div/>').css( {
- width: column.sWidthOrig,
+ if ( scrollX ) {
+ $( this ).append( $('<div/>').css( {
+ width: width,
margin: 0,
padding: 0,
border: 0,
@@ -5762,96 +5253,96 @@
} ) );
}
}
-
- // Find the widest cell for each column and put it into the table
- if ( oSettings.aoData.length ) {
- for ( i=0 ; i<visibleColumns.length ; i++ ) {
- columnIdx = visibleColumns[i];
- column = columns[ columnIdx ];
-
- $( _fnGetWidestNode( oSettings, columnIdx ) )
- .clone( false )
- .append( column.sContentPadding )
- .appendTo( tr );
- }
- }
-
- // Tidy the temporary table - remove name attributes so there aren't
- // duplicated in the dom (radio elements for example)
- $('[name]', tmpTable).removeAttr('name');
-
- // Table has been built, attach to the document so we can work with it.
- // A holding element is used, positioned at the top of the container
- // with minimal height, so it has no effect on if the container scrolls
- // or not. Otherwise it might trigger scrolling when it actually isn't
- // needed
- var holder = $('<div/>').css( scrollX || scrollY ?
- {
- position: 'absolute',
- top: 0,
- left: 0,
- height: 1,
- right: 0,
- overflow: 'hidden'
- } :
- {}
- )
- .append( tmpTable )
- .appendTo( tableContainer );
-
- // When scrolling (X or Y) we want to set the width of the table as
- // appropriate. However, when not scrolling leave the table width as it
- // is. This results in slightly different, but I think correct behaviour
- if ( scrollX && scrollXInner ) {
- tmpTable.width( scrollXInner );
+ else {
+ this.style.width = '';
}
- else if ( scrollX ) {
- tmpTable.css( 'width', 'auto' );
- tmpTable.removeAttr('width');
+ } );
- // If there is no width attribute or style, then allow the table to
- // collapse
- if ( tmpTable.width() < tableContainer.clientWidth && tableWidthAttr ) {
- tmpTable.width( tableContainer.clientWidth );
- }
- }
- else if ( scrollY ) {
+ // Find the widest piece of data for each column and put it into the table
+ for ( i=0 ; i<visibleColumns.length ; i++ ) {
+ columnIdx = visibleColumns[i];
+ column = columns[ columnIdx ];
+
+ var longest = _fnGetMaxLenString(settings, columnIdx);
+ var autoClass = _ext.type.className[column.sType];
+ var text = longest + column.sContentPadding;
+ var insert = longest.indexOf('<') === -1
+ ? document.createTextNode(text)
+ : text
+
+ $('<td/>')
+ .addClass(autoClass)
+ .addClass(column.sClass)
+ .append(insert)
+ .appendTo(tr);
+ }
+
+ // Tidy the temporary table - remove name attributes so there aren't
+ // duplicated in the dom (radio elements for example)
+ $('[name]', tmpTable).removeAttr('name');
+
+ // Table has been built, attach to the document so we can work with it.
+ // A holding element is used, positioned at the top of the container
+ // with minimal height, so it has no effect on if the container scrolls
+ // or not. Otherwise it might trigger scrolling when it actually isn't
+ // needed
+ var holder = $('<div/>').css( scrollX || scrollY ?
+ {
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ height: 1,
+ right: 0,
+ overflow: 'hidden'
+ } :
+ {}
+ )
+ .append( tmpTable )
+ .appendTo( tableContainer );
+
+ // When scrolling (X or Y) we want to set the width of the table as
+ // appropriate. However, when not scrolling leave the table width as it
+ // is. This results in slightly different, but I think correct behaviour
+ if ( scrollX && scrollXInner ) {
+ tmpTable.width( scrollXInner );
+ }
+ else if ( scrollX ) {
+ tmpTable.css( 'width', 'auto' );
+ tmpTable.removeAttr('width');
+
+ // If there is no width attribute or style, then allow the table to
+ // collapse
+ if ( tmpTable.width() < tableContainer.clientWidth && tableWidthAttr ) {
tmpTable.width( tableContainer.clientWidth );
}
- else if ( tableWidthAttr ) {
- tmpTable.width( tableWidthAttr );
- }
+ }
+ else if ( scrollY ) {
+ tmpTable.width( tableContainer.clientWidth );
+ }
+ else if ( tableWidthAttr ) {
+ tmpTable.width( tableWidthAttr );
+ }
- // Get the width of each column in the constructed table - we need to
- // know the inner width (so it can be assigned to the other table's
- // cells) and the outer width so we can calculate the full width of the
- // table. This is safe since DataTables requires a unique cell for each
- // column, but if ever a header can span multiple columns, this will
- // need to be modified.
- var total = 0;
- for ( i=0 ; i<visibleColumns.length ; i++ ) {
- var cell = $(headerCells[i]);
- var border = cell.outerWidth() - cell.width();
+ // Get the width of each column in the constructed table
+ var total = 0;
+ var bodyCells = tmpTable.find('tbody tr').eq(0).children();
- // Use getBounding... where possible (not IE8-) because it can give
- // sub-pixel accuracy, which we then want to round up!
- var bounding = browser.bBounding ?
- Math.ceil( headerCells[i].getBoundingClientRect().width ) :
- cell.outerWidth();
+ for ( i=0 ; i<visibleColumns.length ; i++ ) {
+ // Use getBounding for sub-pixel accuracy, which we then want to round up!
+ var bounding = bodyCells[i].getBoundingClientRect().width;
- // Total is tracked to remove any sub-pixel errors as the outerWidth
- // of the table might not equal the total given here (IE!).
- total += bounding;
+ // Total is tracked to remove any sub-pixel errors as the outerWidth
+ // of the table might not equal the total given here
+ total += bounding;
- // Width for each column to use
- columns[ visibleColumns[i] ].sWidth = _fnStringToCss( bounding - border );
- }
+ // Width for each column to use
+ columns[ visibleColumns[i] ].sWidth = _fnStringToCss( bounding );
+ }
- table.style.width = _fnStringToCss( total );
+ table.style.width = _fnStringToCss( total );
- // Finished with the table - ditch it
- holder.remove();
- }
+ // Finished with the table - ditch it
+ holder.remove();
// If there is a width attr, we want to attach an event listener which
// allows the table sizing to automatically adjust when the window is
@@ -5861,80 +5352,19 @@
table.style.width = _fnStringToCss( tableWidthAttr );
}
- if ( (tableWidthAttr || scrollX) && ! oSettings._reszEvt ) {
+ if ( (tableWidthAttr || scrollX) && ! settings._reszEvt ) {
var bindResize = function () {
- $(window).on('resize.DT-'+oSettings.sInstance, _fnThrottle( function () {
- _fnAdjustColumnSizing( oSettings );
+ $(window).on('resize.DT-'+settings.sInstance, DataTable.util.throttle( function () {
+ if (! settings.bDestroying) {
+ _fnAdjustColumnSizing( settings );
+ }
} ) );
};
- // IE6/7 will crash if we bind a resize event handler on page load.
- // To be removed in 1.11 which drops IE6/7 support
- if ( ie67 ) {
- setTimeout( bindResize, 1000 );
- }
- else {
- bindResize();
- }
-
- oSettings._reszEvt = true;
- }
- }
-
-
- /**
- * Throttle the calls to a function. Arguments and context are maintained for
- * the throttled function
- * @param {function} fn Function to be called
- * @param {int} [freq=200] call frequency in mS
- * @returns {function} wrapped function
- * @memberof DataTable#oApi
- */
- var _fnThrottle = DataTable.util.throttle;
-
-
- /**
- * Convert a CSS unit width to pixels (e.g. 2em)
- * @param {string} width width to be converted
- * @param {node} parent parent to get the with for (required for relative widths) - optional
- * @returns {int} width in pixels
- * @memberof DataTable#oApi
- */
- function _fnConvertToWidth ( width, parent )
- {
- if ( ! width ) {
- return 0;
- }
-
- var n = $('<div/>')
- .css( 'width', _fnStringToCss( width ) )
- .appendTo( parent || document.body );
-
- var val = n[0].offsetWidth;
- n.remove();
-
- return val;
- }
-
+ bindResize();
- /**
- * Get the widest node
- * @param {object} settings dataTables settings object
- * @param {int} colIdx column of interest
- * @returns {node} widest table node
- * @memberof DataTable#oApi
- */
- function _fnGetWidestNode( settings, colIdx )
- {
- var idx = _fnGetMaxLenString( settings, colIdx );
- if ( idx < 0 ) {
- return null;
+ settings._reszEvt = true;
}
-
- var data = settings.aoData[ idx ];
- return ! data.nTr ? // Might not have been created when deferred rendering
- $('<td/>').html( _fnGetCellData( settings, idx, colIdx, 'display' ) )[0] :
- data.anCells[ colIdx ];
}
@@ -5942,25 +5372,45 @@
* Get the maximum strlen for each data column
* @param {object} settings dataTables settings object
* @param {int} colIdx column of interest
- * @returns {string} max string length for each column
+ * @returns {string} string of the max length
* @memberof DataTable#oApi
*/
function _fnGetMaxLenString( settings, colIdx )
{
- var s, max=-1, maxIdx = -1;
-
- for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
- s = _fnGetCellData( settings, i, colIdx, 'display' )+'';
- s = s.replace( __re_html_remove, '' );
- s = s.replace( / /g, ' ' );
+ var column = settings.aoColumns[colIdx];
- if ( s.length > max ) {
- max = s.length;
- maxIdx = i;
+ if (! column.maxLenString) {
+ var s, max='', maxLen = -1;
+
+ for ( var i=0, ien=settings.aiDisplayMaster.length ; i<ien ; i++ ) {
+ var rowIdx = settings.aiDisplayMaster[i];
+ var data = _fnGetRowDisplay(settings, rowIdx)[colIdx];
+
+ var cellString = data && typeof data === 'object' && data.nodeType
+ ? data.innerHTML
+ : data+'';
+
+ // Remove id / name attributes from elements so they
+ // don't interfere with existing elements
+ cellString = cellString
+ .replace(/id=".*?"/g, '')
+ .replace(/name=".*?"/g, '');
+
+ s = _stripHtml(cellString)
+ .replace( / /g, ' ' );
+
+ if ( s.length > maxLen ) {
+ // We want the HTML in the string, but the length that
+ // is important is the stripped string
+ max = cellString;
+ maxLen = s.length;
+ }
}
+
+ column.maxLenString = max;
}
- return maxIdx;
+ return column.maxLenString;
}
@@ -5988,68 +5438,229 @@
s;
}
+ /**
+ * Re-insert the `col` elements for current visibility
+ *
+ * @param {*} settings DT settings
+ */
+ function _colGroup( settings ) {
+ var cols = settings.aoColumns;
+
+ settings.colgroup.empty();
+
+ for (i=0 ; i<cols.length ; i++) {
+ if (cols[i].bVisible) {
+ settings.colgroup.append(cols[i].colEl);
+ }
+ }
+ }
+
+
+ function _fnSortInit( settings ) {
+ var target = settings.nTHead;
+ var headerRows = target.querySelectorAll('tr');
+ var legacyTop = settings.bSortCellsTop;
+ var notSelector = ':not([data-dt-order="disable"]):not([data-dt-order="icon-only"])';
+
+ // Legacy support for `orderCellsTop`
+ if (legacyTop === true) {
+ target = headerRows[0];
+ }
+ else if (legacyTop === false) {
+ target = headerRows[ headerRows.length - 1 ];
+ }
+
+ _fnSortAttachListener(
+ settings,
+ target,
+ target === settings.nTHead
+ ? 'tr'+notSelector+' th'+notSelector+', tr'+notSelector+' td'+notSelector
+ : 'th'+notSelector+', td'+notSelector
+ );
+
+ // Need to resolve the user input array into our internal structure
+ var order = [];
+ _fnSortResolve( settings, order, settings.aaSorting );
+
+ settings.aaSorting = order;
+ }
+
+
+ function _fnSortAttachListener(settings, node, selector, column, callback) {
+ _fnBindAction( node, selector, function (e) {
+ var run = false;
+ var columns = column === undefined
+ ? _fnColumnsFromHeader( e.target )
+ : [column];
+
+ if ( columns.length ) {
+ for ( var i=0, ien=columns.length ; i<ien ; i++ ) {
+ var ret = _fnSortAdd( settings, columns[i], i, e.shiftKey );
+
+ if (ret !== false) {
+ run = true;
+ }
+
+ // If the first entry is no sort, then subsequent
+ // sort columns are ignored
+ if (settings.aaSorting.length === 1 && settings.aaSorting[0][1] === '') {
+ break;
+ }
+ }
+
+ if (run) {
+ _fnProcessingDisplay( settings, true );
+
+ // Allow the processing display to show
+ setTimeout( function () {
+ _fnSort( settings );
+ _fnSortDisplay( settings, settings.aiDisplay );
+
+ // Sort processing done - redraw has its own processing display
+ _fnProcessingDisplay( settings, false );
+
+ _fnReDraw( settings, false, false );
+
+ if (callback) {
+ callback();
+ }
+ }, 0);
+ }
+ }
+ } );
+ }
+
+ /**
+ * Sort the display array to match the master's order
+ * @param {*} settings
+ */
+ function _fnSortDisplay(settings, display) {
+ if (display.length < 2) {
+ return;
+ }
+
+ var master = settings.aiDisplayMaster;
+ var masterMap = {};
+ var map = {};
+ var i;
+
+ // Rather than needing an `indexOf` on master array, we can create a map
+ for (i=0 ; i<master.length ; i++) {
+ masterMap[master[i]] = i;
+ }
+
+ // And then cache what would be the indexOf fom the display
+ for (i=0 ; i<display.length ; i++) {
+ map[display[i]] = masterMap[display[i]];
+ }
+
+ display.sort(function(a, b){
+ // Short version of this function is simply `master.indexOf(a) - master.indexOf(b);`
+ return map[a] - map[b];
+ });
+ }
+
+
+ function _fnSortResolve (settings, nestedSort, sort) {
+ var push = function ( a ) {
+ if ($.isPlainObject(a)) {
+ if (a.idx !== undefined) {
+ // Index based ordering
+ nestedSort.push([a.idx, a.dir]);
+ }
+ else if (a.name) {
+ // Name based ordering
+ var cols = _pluck( settings.aoColumns, 'sName');
+ var idx = cols.indexOf(a.name);
+
+ if (idx !== -1) {
+ nestedSort.push([idx, a.dir]);
+ }
+ }
+ }
+ else {
+ // Plain column index and direction pair
+ nestedSort.push(a);
+ }
+ };
+
+ if ( $.isPlainObject(sort) ) {
+ // Object
+ push(sort);
+ }
+ else if ( sort.length && typeof sort[0] === 'number' ) {
+ // 1D array
+ push(sort);
+ }
+ else if ( sort.length ) {
+ // 2D array
+ for (var z=0; z<sort.length; z++) {
+ push(sort[z]); // Object or array
+ }
+ }
+ }
function _fnSortFlatten ( settings )
{
var
- i, iLen, k, kLen,
+ i, k, kLen,
aSort = [],
- aiOrig = [],
+ extSort = DataTable.ext.type.order,
aoColumns = settings.aoColumns,
aDataSort, iCol, sType, srcCol,
fixed = settings.aaSortingFixed,
fixedObj = $.isPlainObject( fixed ),
- nestedSort = [],
- add = function ( a ) {
- if ( a.length && ! Array.isArray( a[0] ) ) {
- // 1D array
- nestedSort.push( a );
- }
- else {
- // 2D array
- $.merge( nestedSort, a );
- }
- };
+ nestedSort = [];
+
+ if ( ! settings.oFeatures.bSort ) {
+ return aSort;
+ }
// Build the sort array, with pre-fix and post-fix options if they have been
// specified
if ( Array.isArray( fixed ) ) {
- add( fixed );
+ _fnSortResolve( settings, nestedSort, fixed );
}
if ( fixedObj && fixed.pre ) {
- add( fixed.pre );
+ _fnSortResolve( settings, nestedSort, fixed.pre );
}
- add( settings.aaSorting );
+ _fnSortResolve( settings, nestedSort, settings.aaSorting );
if (fixedObj && fixed.post ) {
- add( fixed.post );
+ _fnSortResolve( settings, nestedSort, fixed.post );
}
for ( i=0 ; i<nestedSort.length ; i++ )
{
srcCol = nestedSort[i][0];
- aDataSort = aoColumns[ srcCol ].aDataSort;
- for ( k=0, kLen=aDataSort.length ; k<kLen ; k++ )
- {
- iCol = aDataSort[k];
- sType = aoColumns[ iCol ].sType || 'string';
+ if ( aoColumns[ srcCol ] ) {
+ aDataSort = aoColumns[ srcCol ].aDataSort;
- if ( nestedSort[i]._idx === undefined ) {
- nestedSort[i]._idx = $.inArray( nestedSort[i][1], aoColumns[iCol].asSorting );
- }
+ for ( k=0, kLen=aDataSort.length ; k<kLen ; k++ )
+ {
+ iCol = aDataSort[k];
+ sType = aoColumns[ iCol ].sType || 'string';
- aSort.push( {
- src: srcCol,
- col: iCol,
- dir: nestedSort[i][1],
- index: nestedSort[i]._idx,
- type: sType,
- formatter: DataTable.ext.type.order[ sType+"-pre" ]
- } );
+ if ( nestedSort[i]._idx === undefined ) {
+ nestedSort[i]._idx = aoColumns[iCol].asSorting.indexOf(nestedSort[i][1]);
+ }
+
+ if ( nestedSort[i][1] ) {
+ aSort.push( {
+ src: srcCol,
+ col: iCol,
+ dir: nestedSort[i][1],
+ index: nestedSort[i]._idx,
+ type: sType,
+ formatter: extSort[ sType+"-pre" ],
+ sorter: extSort[ sType+"-"+nestedSort[i][1] ]
+ } );
+ }
+ }
}
}
@@ -6060,19 +5671,14 @@
* Change the order of the table
* @param {object} oSettings dataTables settings object
* @memberof DataTable#oApi
- * @todo This really needs split up!
*/
- function _fnSort ( oSettings )
+ function _fnSort ( oSettings, col, dir )
{
var
- i, ien, iLen, j, jLen, k, kLen,
- sDataType, nTh,
+ i, ien, iLen,
aiOrig = [],
- oExtSort = DataTable.ext.type.order,
+ extSort = DataTable.ext.type.order,
aoData = oSettings.aoData,
- aoColumns = oSettings.aoColumns,
- aDataSort, data, iCol, sType, oSort,
- formatters = 0,
sortCol,
displayMaster = oSettings.aiDisplayMaster,
aSort;
@@ -6082,16 +5688,28 @@
// data is going to be used in the table?
_fnColumnTypes( oSettings );
- aSort = _fnSortFlatten( oSettings );
+ // Allow a specific column to be sorted, which will _not_ alter the display
+ // master
+ if (col !== undefined) {
+ var srcCol = oSettings.aoColumns[col];
+ aSort = [{
+ src: col,
+ col: col,
+ dir: dir,
+ index: 0,
+ type: srcCol.sType,
+ formatter: extSort[ srcCol.sType+"-pre" ],
+ sorter: extSort[ srcCol.sType+"-"+dir ]
+ }];
+ displayMaster = displayMaster.slice();
+ }
+ else {
+ aSort = _fnSortFlatten( oSettings );
+ }
for ( i=0, ien=aSort.length ; i<ien ; i++ ) {
sortCol = aSort[i];
- // Track if we can use the fast sort algorithm
- if ( sortCol.formatter ) {
- formatters++;
- }
-
// Load the data needed for the sort, for each cell
_fnSortData( oSettings, sortCol.col );
}
@@ -6099,137 +5717,88 @@
/* No sorting required if server-side or no sorting array */
if ( _fnDataSource( oSettings ) != 'ssp' && aSort.length !== 0 )
{
- // Create a value - key array of the current row positions such that we can use their
- // current position during the sort, if values match, in order to perform stable sorting
+ // Reset the initial positions on each pass so we get a stable sort
for ( i=0, iLen=displayMaster.length ; i<iLen ; i++ ) {
- aiOrig[ displayMaster[i] ] = i;
+ aiOrig[ i ] = i;
+ }
+
+ // If the first sort is desc, then reverse the array to preserve original
+ // order, just in reverse
+ if (aSort.length && aSort[0].dir === 'desc') {
+ aiOrig.reverse();
}
/* Do the sort - here we want multi-column sorting based on a given data source (column)
* and sorting function (from oSort) in a certain direction. It's reasonably complex to
* follow on it's own, but this is what we want (example two column sorting):
* fnLocalSorting = function(a,b){
- * var iTest;
- * iTest = oSort['string-asc']('data11', 'data12');
- * if (iTest !== 0)
- * return iTest;
- * iTest = oSort['numeric-desc']('data21', 'data22');
- * if (iTest !== 0)
- * return iTest;
+ * var test;
+ * test = oSort['string-asc']('data11', 'data12');
+ * if (test !== 0)
+ * return test;
+ * test = oSort['numeric-desc']('data21', 'data22');
+ * if (test !== 0)
+ * return test;
* return oSort['numeric-asc']( aiOrig[a], aiOrig[b] );
* }
* Basically we have a test for each sorting column, if the data in that column is equal,
* test the next column. If all columns match, then we use a numeric sort on the row
* positions in the original data array to provide a stable sort.
- *
- * Note - I know it seems excessive to have two sorting methods, but the first is around
- * 15% faster, so the second is only maintained for backwards compatibility with sorting
- * methods which do not have a pre-sort formatting function.
*/
- if ( formatters === aSort.length ) {
- // All sort types have formatting functions
- displayMaster.sort( function ( a, b ) {
- var
- x, y, k, test, sort,
- len=aSort.length,
- dataA = aoData[a]._aSortData,
- dataB = aoData[b]._aSortData;
+ displayMaster.sort( function ( a, b ) {
+ var
+ x, y, k, test, sort,
+ len=aSort.length,
+ dataA = aoData[a]._aSortData,
+ dataB = aoData[b]._aSortData;
- for ( k=0 ; k<len ; k++ ) {
- sort = aSort[k];
+ for ( k=0 ; k<len ; k++ ) {
+ sort = aSort[k];
- x = dataA[ sort.col ];
- y = dataB[ sort.col ];
+ // Data, which may have already been through a `-pre` function
+ x = dataA[ sort.col ];
+ y = dataB[ sort.col ];
+
+ if (sort.sorter) {
+ // If there is a custom sorter (`-asc` or `-desc`) for this
+ // data type, use it
+ test = sort.sorter(x, y);
- test = x<y ? -1 : x>y ? 1 : 0;
if ( test !== 0 ) {
- return sort.dir === 'asc' ? test : -test;
+ return test;
}
}
+ else {
+ // Otherwise, use generic sorting
+ test = x<y ? -1 : x>y ? 1 : 0;
- x = aiOrig[a];
- y = aiOrig[b];
- return x<y ? -1 : x>y ? 1 : 0;
- } );
- }
- else {
- // Depreciated - remove in 1.11 (providing a plug-in option)
- // Not all sort types have formatting methods, so we have to call their sorting
- // methods.
- displayMaster.sort( function ( a, b ) {
- var
- x, y, k, l, test, sort, fn,
- len=aSort.length,
- dataA = aoData[a]._aSortData,
- dataB = aoData[b]._aSortData;
-
- for ( k=0 ; k<len ; k++ ) {
- sort = aSort[k];
-
- x = dataA[ sort.col ];
- y = dataB[ sort.col ];
-
- fn = oExtSort[ sort.type+"-"+sort.dir ] || oExtSort[ "string-"+sort.dir ];
- test = fn( x, y );
if ( test !== 0 ) {
- return test;
+ return sort.dir === 'asc' ? test : -test;
}
}
+ }
- x = aiOrig[a];
- y = aiOrig[b];
- return x<y ? -1 : x>y ? 1 : 0;
- } );
- }
- }
-
- /* Tell the draw function that we have sorted the data */
- oSettings.bSorted = true;
- }
-
-
- function _fnSortAria ( settings )
- {
- var label;
- var nextSort;
- var columns = settings.aoColumns;
- var aSort = _fnSortFlatten( settings );
- var oAria = settings.oLanguage.oAria;
-
- // ARIA attributes - need to loop all columns, to update all (removing old
- // attributes as needed)
- for ( var i=0, iLen=columns.length ; i<iLen ; i++ )
- {
- var col = columns[i];
- var asSorting = col.asSorting;
- var sTitle = col.ariaTitle || col.sTitle.replace( /<.*?>/g, "" );
- var th = col.nTh;
-
- // IE7 is throwing an error when setting these properties with jQuery's
- // attr() and removeAttr() methods...
- th.removeAttribute('aria-sort');
+ x = aiOrig[a];
+ y = aiOrig[b];
- /* In ARIA only the first sorting column can be marked as sorting - no multi-sort option */
- if ( col.bSortable ) {
- if ( aSort.length > 0 && aSort[0].col == i ) {
- th.setAttribute('aria-sort', aSort[0].dir=="asc" ? "ascending" : "descending" );
- nextSort = asSorting[ aSort[0].index+1 ] || asSorting[0];
- }
- else {
- nextSort = asSorting[0];
- }
+ return x<y ? -1 : x>y ? 1 : 0;
+ } );
+ }
+ else if ( aSort.length === 0 ) {
+ // Apply index order
+ displayMaster.sort(function (x, y) {
+ return x<y ? -1 : x>y ? 1 : 0;
+ });
+ }
- label = sTitle + ( nextSort === "asc" ?
- oAria.sSortAscending :
- oAria.sSortDescending
- );
- }
- else {
- label = sTitle;
- }
+ if (col === undefined) {
+ // Tell the draw function that we have sorted the data
+ oSettings.bSorted = true;
- th.setAttribute('aria-label', label);
+ _fnCallbackFire( oSettings, null, 'order', [oSettings, aSort] );
}
+
+ return displayMaster;
}
@@ -6238,12 +5807,12 @@
* @param {object} settings dataTables settings object
* @param {node} attachTo node to attach the handler to
* @param {int} colIdx column sorting index
- * @param {boolean} [append=false] Append the requested sort to the existing
- * sort if true (i.e. multi-column sort)
+ * @param {int} addIndex Counter
+ * @param {boolean} [shift=false] Shift click add
* @param {function} [callback] callback function
* @memberof DataTable#oApi
*/
- function _fnSortListener ( settings, colIdx, append, callback )
+ function _fnSortAdd ( settings, colIdx, addIndex, shift )
{
var col = settings.aoColumns[ colIdx ];
var sorting = settings.aaSorting;
@@ -6252,7 +5821,7 @@
var next = function ( a, overflow ) {
var idx = a._idx;
if ( idx === undefined ) {
- idx = $.inArray( a[1], asSorting );
+ idx = asSorting.indexOf(a[1]);
}
return idx+1 < asSorting.length ?
@@ -6262,15 +5831,19 @@
0;
};
+ if ( ! col.bSortable ) {
+ return false;
+ }
+
// Convert to 2D array if needed
if ( typeof sorting[0] === 'number' ) {
sorting = settings.aaSorting = [ sorting ];
}
// If appending the sort then we are multi-column sorting
- if ( append && settings.oFeatures.bSortMulti ) {
+ if ( (shift || addIndex) && settings.oFeatures.bSortMulti ) {
// Are we already doing some kind of sort on this column?
- var sortIdx = $.inArray( colIdx, _pluck(sorting, '0') );
+ var sortIdx = _pluck(sorting, '0').indexOf(colIdx);
if ( sortIdx !== -1 ) {
// Yes, modify the sort
@@ -6288,11 +5861,18 @@
sorting[sortIdx]._idx = nextSortIdx;
}
}
- else {
- // No sort on this column yet
+ else if (shift) {
+ // No sort on this column yet, being added by shift click
+ // add it as itself
sorting.push( [ colIdx, asSorting[0], 0 ] );
sorting[sorting.length-1]._idx = 0;
}
+ else {
+ // No sort on this column yet, being added from a colspan
+ // so add with same direction as first column
+ sorting.push( [ colIdx, sorting[0][1], 0 ] );
+ sorting[sorting.length-1]._idx = 0;
+ }
}
else if ( sorting.length && sorting[0][0] == colIdx ) {
// Single column - already sorting on this column, modify the sort
@@ -6308,54 +5888,6 @@
sorting.push( [ colIdx, asSorting[0] ] );
sorting[0]._idx = 0;
}
-
- // Run the sort by calling a full redraw
- _fnReDraw( settings );
-
- // callback used for async user interaction
- if ( typeof callback == 'function' ) {
- callback( settings );
- }
- }
-
-
- /**
- * Attach a sort handler (click) to a node
- * @param {object} settings dataTables settings object
- * @param {node} attachTo node to attach the handler to
- * @param {int} colIdx column sorting index
- * @param {function} [callback] callback function
- * @memberof DataTable#oApi
- */
- function _fnSortAttachListener ( settings, attachTo, colIdx, callback )
- {
- var col = settings.aoColumns[ colIdx ];
-
- _fnBindAction( attachTo, {}, function (e) {
- /* If the column is not sortable - don't to anything */
- if ( col.bSortable === false ) {
- return;
- }
-
- // If processing is enabled use a timeout to allow the processing
- // display to be shown - otherwise to it synchronously
- if ( settings.oFeatures.bProcessing ) {
- _fnProcessingDisplay( settings, true );
-
- setTimeout( function() {
- _fnSortListener( settings, colIdx, e.shiftKey, callback );
-
- // In server-side processing, the draw callback will remove the
- // processing display
- if ( _fnDataSource( settings ) !== 'ssp' ) {
- _fnProcessingDisplay( settings, false );
- }
- }, 0 );
- }
- else {
- _fnSortListener( settings, colIdx, e.shiftKey, callback );
- }
- } );
}
@@ -6368,7 +5900,7 @@
function _fnSortingClasses( settings )
{
var oldSort = settings.aLastSort;
- var sortClass = settings.oClasses.sSortColumn;
+ var sortClass = settings.oClasses.order.position;
var sort = _fnSortFlatten( settings );
var features = settings.oFeatures;
var i, ien, colIdx;
@@ -6398,48 +5930,54 @@
// Get the data to sort a column, be it from cache, fresh (populating the
// cache), or from a sort formatter
- function _fnSortData( settings, idx )
+ function _fnSortData( settings, colIdx )
{
// Custom sorting function - provided by the sort data type
- var column = settings.aoColumns[ idx ];
+ var column = settings.aoColumns[ colIdx ];
var customSort = DataTable.ext.order[ column.sSortDataType ];
var customData;
if ( customSort ) {
- customData = customSort.call( settings.oInstance, settings, idx,
- _fnColumnIndexToVisible( settings, idx )
+ customData = customSort.call( settings.oInstance, settings, colIdx,
+ _fnColumnIndexToVisible( settings, colIdx )
);
}
// Use / populate cache
var row, cellData;
var formatter = DataTable.ext.type.order[ column.sType+"-pre" ];
+ var data = settings.aoData;
+
+ for ( var rowIdx=0 ; rowIdx<data.length ; rowIdx++ ) {
+ // Sparse array
+ if (! data[rowIdx]) {
+ continue;
+ }
- for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
- row = settings.aoData[i];
+ row = data[rowIdx];
if ( ! row._aSortData ) {
row._aSortData = [];
}
- if ( ! row._aSortData[idx] || customSort ) {
+ if ( ! row._aSortData[colIdx] || customSort ) {
cellData = customSort ?
- customData[i] : // If there was a custom sort function, use data from there
- _fnGetCellData( settings, i, idx, 'sort' );
+ customData[rowIdx] : // If there was a custom sort function, use data from there
+ _fnGetCellData( settings, rowIdx, colIdx, 'sort' );
- row._aSortData[ idx ] = formatter ?
- formatter( cellData ) :
+ row._aSortData[ colIdx ] = formatter ?
+ formatter( cellData, settings ) :
cellData;
}
}
}
-
/**
- * Save the state of a table
- * @param {object} oSettings dataTables settings object
- * @memberof DataTable#oApi
+ * State information for a table
+ *
+ * @param {*} settings
+ * @returns State object
*/
function _fnSaveState ( settings )
{
@@ -6453,11 +5991,11 @@
start: settings._iDisplayStart,
length: settings._iDisplayLength,
order: $.extend( true, [], settings.aaSorting ),
- search: _fnSearchToCamel( settings.oPreviousSearch ),
- columns: $.map( settings.aoColumns, function ( col, i ) {
+ search: $.extend({}, settings.oPreviousSearch),
+ columns: settings.aoColumns.map( function ( col, i ) {
return {
visible: col.bVisible,
- search: _fnSearchToCamel( settings.aoPreSearchCols[i] )
+ search: $.extend({}, settings.aoPreSearchCols[i])
};
} )
};
@@ -6479,7 +6017,7 @@
* @param {function} callback Callback to execute when the state has been loaded
* @memberof DataTable#oApi
*/
- function _fnLoadState ( settings, oInit, callback )
+ function _fnLoadState ( settings, init, callback )
{
if ( ! settings.oFeatures.bStateSave ) {
callback();
@@ -6515,18 +6053,18 @@
return;
}
- // Allow custom and plug-in manipulation functions to alter the saved data set and
- // cancelling of loading by returning false
- var abStateLoad = _fnCallbackFire( settings, 'aoStateLoadParams', 'stateLoadParams', [settings, s] );
- if ( $.inArray( false, abStateLoad ) !== -1 ) {
+ // Reject old data
+ var duration = settings.iStateDuration;
+ if ( duration > 0 && s.time < +new Date() - (duration*1000) ) {
settings._bLoadingState = false;
callback();
return;
}
- // Reject old data
- var duration = settings.iStateDuration;
- if ( duration > 0 && s.time < +new Date() - (duration*1000) ) {
+ // Allow custom and plug-in manipulation functions to alter the saved data set and
+ // cancelling of loading by returning false
+ var abStateLoad = _fnCallbackFire( settings, 'aoStateLoadParams', 'stateLoadParams', [settings, s] );
+ if ( abStateLoad.indexOf(false) !== -1 ) {
settings._bLoadingState = false;
callback();
return;
@@ -6542,6 +6080,10 @@
// Store the saved state so it might be accessed at any time
settings.oLoadedState = $.extend( true, {}, s );
+ // This is needed for ColReorder, which has to happen first to allow all
+ // the stored indexes to be usable. It is not publicly documented.
+ _fnCallbackFire( settings, null, 'stateLoadInit', [settings, s], true );
+
// Page Length
if ( s.length !== undefined ) {
// If already initialised just set the value directly so that the select element is also updated
@@ -6578,7 +6120,7 @@
// Search
if ( s.search !== undefined ) {
- $.extend( settings.oPreviousSearch, _fnSearchToHung( s.search ) );
+ $.extend( settings.oPreviousSearch, s.search );
}
// Columns
@@ -6600,7 +6142,7 @@
// Search
if ( col.search !== undefined ) {
- $.extend( settings.aoPreSearchCols[i], _fnSearchToHung( col.search ) );
+ $.extend( settings.aoPreSearchCols[i], col.search );
}
}
@@ -6613,26 +6155,8 @@
settings._bLoadingState = false;
_fnCallbackFire( settings, 'aoStateLoaded', 'stateLoaded', [settings, s] );
callback();
- };
-
-
- /**
- * Return the settings object for a particular table
- * @param {node} table table we are using as a dataTable
- * @returns {object} Settings object - or null if not found
- * @memberof DataTable#oApi
- */
- function _fnSettingsFromNode ( table )
- {
- var settings = DataTable.settings;
- var idx = $.inArray( table, _pluck( settings, 'nTable' ) );
-
- return idx !== -1 ?
- settings[ idx ] :
- null;
}
-
/**
* Log an error message
* @param {object} settings dataTables settings object
@@ -6648,7 +6172,7 @@
if ( tn ) {
msg += '. For more information about this error, please see '+
- 'http://datatables.net/tn/'+tn;
+ 'https://datatables.net/tn/'+tn;
}
if ( ! level ) {
@@ -6657,7 +6181,7 @@
var type = ext.sErrMode || ext.errMode;
if ( settings ) {
- _fnCallbackFire( settings, null, 'error', [ settings, tn, msg ] );
+ _fnCallbackFire( settings, null, 'dt-error', [ settings, tn, msg ], true );
}
if ( type == 'alert' ) {
@@ -6731,7 +6255,7 @@
var val;
for ( var prop in extender ) {
- if ( extender.hasOwnProperty(prop) ) {
+ if ( Object.prototype.hasOwnProperty.call(extender, prop) ) {
val = extender[prop];
if ( $.isPlainObject( val ) ) {
@@ -6758,47 +6282,42 @@
* This is good for accessibility since a return on the keyboard will have the
* same effect as a click, if the element has focus.
* @param {element} n Element to bind the action to
- * @param {object} oData Data object to pass to the triggered function
+ * @param {object|string} selector Selector (for delegated events) or data object
+ * to pass to the triggered function
* @param {function} fn Callback function for when the event is triggered
* @memberof DataTable#oApi
*/
- function _fnBindAction( n, oData, fn )
+ function _fnBindAction( n, selector, fn )
{
$(n)
- .on( 'click.DT', oData, function (e) {
- $(n).trigger('blur'); // Remove focus outline for mouse users
+ .on( 'click.DT', selector, function (e) {
+ fn(e);
+ } )
+ .on( 'keypress.DT', selector, function (e){
+ if ( e.which === 13 ) {
+ e.preventDefault();
fn(e);
- } )
- .on( 'keypress.DT', oData, function (e){
- if ( e.which === 13 ) {
- e.preventDefault();
- fn(e);
- }
- } )
- .on( 'selectstart.DT', function () {
- /* Take the brutal approach to cancelling text selection */
- return false;
- } );
+ }
+ } )
+ .on( 'selectstart.DT', selector, function () {
+ // Don't want a double click resulting in text selection
+ return false;
+ } );
}
/**
* Register a callback function. Easily allows a callback function to be added to
* an array store of callback functions that can then all be called together.
- * @param {object} oSettings dataTables settings object
- * @param {string} sStore Name of the array storage for the callbacks in oSettings
+ * @param {object} settings dataTables settings object
+ * @param {string} store Name of the array storage for the callbacks in oSettings
* @param {function} fn Function to be called back
- * @param {string} sName Identifying name for the callback (i.e. a label)
* @memberof DataTable#oApi
*/
- function _fnCallbackReg( oSettings, sStore, fn, sName )
+ function _fnCallbackReg( settings, store, fn )
{
- if ( fn )
- {
- oSettings[sStore].push( {
- "fn": fn,
- "sName": sName
- } );
+ if ( fn ) {
+ settings[store].push(fn);
}
}
@@ -6815,27 +6334,31 @@
* null no trigger is fired
* @param {array} args Array of arguments to pass to the callback function /
* trigger
+ * @param {boolean} [bubbles] True if the event should bubble
* @memberof DataTable#oApi
*/
- function _fnCallbackFire( settings, callbackArr, eventName, args )
+ function _fnCallbackFire( settings, callbackArr, eventName, args, bubbles )
{
var ret = [];
if ( callbackArr ) {
- ret = $.map( settings[callbackArr].slice().reverse(), function (val, i) {
- return val.fn.apply( settings.oInstance, args );
+ ret = settings[callbackArr].slice().reverse().map( function (val) {
+ return val.apply( settings.oInstance, args );
} );
}
- if ( eventName !== null ) {
+ if ( eventName !== null) {
var e = $.Event( eventName+'.dt' );
var table = $(settings.nTable);
+
+ // Expose the DataTables API on the event object for easy access
+ e.dt = settings.api;
- table.trigger( e, args );
+ table[bubbles ? 'trigger' : 'triggerHandler']( e, args );
// If not yet attached to the document, trigger the event
// on the body directly to sort of simulate the bubble
- if (table.parents('body').length === 0) {
+ if (bubbles && table.parents('body').length === 0) {
$('body').trigger( e, args );
}
@@ -6905,12 +6428,43 @@
if ( settings.oFeatures.bServerSide ) {
return 'ssp';
}
- else if ( settings.ajax || settings.sAjaxSource ) {
+ else if ( settings.ajax ) {
return 'ajax';
}
return 'dom';
}
+ /**
+ * Common replacement for language strings
+ *
+ * @param {*} settings DT settings object
+ * @param {*} str String with values to replace
+ * @param {*} entries Plural number for _ENTRIES_ - can be undefined
+ * @returns String
+ */
+ function _fnMacros ( settings, str, entries )
+ {
+ // When infinite scrolling, we are always starting at 1. _iDisplayStart is
+ // used only internally
+ var
+ formatter = settings.fnFormatNumber,
+ start = settings._iDisplayStart+1,
+ len = settings._iDisplayLength,
+ vis = settings.fnRecordsDisplay(),
+ max = settings.fnRecordsTotal(),
+ all = len === -1;
+
+ return str.
+ replace(/_START_/g, formatter.call( settings, start ) ).
+ replace(/_END_/g, formatter.call( settings, settings.fnDisplayEnd() ) ).
+ replace(/_MAX_/g, formatter.call( settings, max ) ).
+ replace(/_TOTAL_/g, formatter.call( settings, vis ) ).
+ replace(/_PAGE_/g, formatter.call( settings, all ? 1 : Math.ceil( start / len ) ) ).
+ replace(/_PAGES_/g, formatter.call( settings, all ? 1 : Math.ceil( vis / len ) ) ).
+ replace(/_ENTRIES_/g, settings.api.i18n('entries', '', entries) ).
+ replace(/_ENTRIES-MAX_/g, settings.api.i18n('entries', '', max) ).
+ replace(/_ENTRIES-TOTAL_/g, settings.api.i18n('entries', '', vis) );
+ }
@@ -6986,20 +6540,18 @@
{
var idx, jq;
var settings = DataTable.settings;
- var tables = $.map( settings, function (el, i) {
- return el.nTable;
- } );
+ var tables = _pluck(settings, 'nTable');
if ( ! mixed ) {
return [];
}
- else if ( mixed.nTable && mixed.oApi ) {
+ else if ( mixed.nTable && mixed.oFeatures ) {
// DataTables settings object
return [ mixed ];
}
else if ( mixed.nodeName && mixed.nodeName.toLowerCase() === 'table' ) {
// Table node
- idx = $.inArray( mixed, tables );
+ idx = tables.indexOf(mixed);
return idx !== -1 ? [ settings[idx] ] : null;
}
else if ( mixed && typeof mixed.settings === 'function' ) {
@@ -7007,18 +6559,17 @@
}
else if ( typeof mixed === 'string' ) {
// jQuery selector
- jq = $(mixed);
+ jq = $(mixed).get();
}
else if ( mixed instanceof $ ) {
// jQuery object (also DataTables instance)
- jq = mixed;
+ jq = mixed.get();
}
if ( jq ) {
- return jq.map( function(i) {
- idx = $.inArray( this, tables );
- return idx !== -1 ? settings[idx] : null;
- } ).toArray();
+ return settings.filter(function (v, idx) {
+ return jq.includes(tables[idx]);
+ });
}
};
@@ -7075,7 +6626,7 @@
*
* @example
* // Initialisation as a constructor
- * var api = new $.fn.DataTable.Api( 'table.dataTable' );
+ * var api = new DataTable.Api( 'table.dataTable' );
*/
_Api = function ( context, data )
{
@@ -7101,11 +6652,13 @@
}
// Remove duplicates
- this.context = _unique( settings );
+ this.context = settings.length > 1
+ ? _unique( settings )
+ : settings;
// Initial data
if ( data ) {
- $.merge( this, data );
+ this.push.apply(this, data);
}
// selector
@@ -7128,19 +6681,13 @@
return this.count() !== 0;
},
-
- concat: __arrayProto.concat,
-
-
context: [], // array of table settings objects
-
count: function ()
{
return this.flatten().length;
},
-
each: function ( fn )
{
for ( var i=0, ien=this.length ; i<ien; i++ ) {
@@ -7150,7 +6697,6 @@
return this;
},
-
eq: function ( idx )
{
var ctx = this.context;
@@ -7160,47 +6706,33 @@
null;
},
-
filter: function ( fn )
{
- var a = [];
-
- if ( __arrayProto.filter ) {
- a = __arrayProto.filter.call( this, fn, this );
- }
- else {
- // Compatibility for browsers without EMCA-252-5 (JS 1.6)
- for ( var i=0, ien=this.length ; i<ien ; i++ ) {
- if ( fn.call( this, this[i], i, this ) ) {
- a.push( this[i] );
- }
- }
- }
+ var a = __arrayProto.filter.call( this, fn, this );
return new _Api( this.context, a );
},
-
flatten: function ()
{
var a = [];
+
return new _Api( this.context, a.concat.apply( a, this.toArray() ) );
},
+ get: function ( idx )
+ {
+ return this[ idx ];
+ },
join: __arrayProto.join,
-
- indexOf: __arrayProto.indexOf || function (obj, start)
- {
- for ( var i=(start || 0), ien=this.length ; i<ien ; i++ ) {
- if ( this[i] === obj ) {
- return i;
- }
- }
- return -1;
+ includes: function ( find ) {
+ return this.indexOf( find ) === -1 ? false : true;
},
+ indexOf: __arrayProto.indexOf,
+
iterator: function ( flatten, type, fn, alwaysNew ) {
var
a = [], ret,
@@ -7235,7 +6767,7 @@
a.push( ret );
}
}
- else if ( type === 'column' || type === 'column-rows' || type === 'row' || type === 'cell' ) {
+ else if ( type === 'every' || type === 'column' || type === 'column-rows' || type === 'row' || type === 'cell' ) {
// columns and rows share the same structure.
// 'this' is an array of column indexes for each context
items = this[i];
@@ -7272,35 +6804,17 @@
return this;
},
-
- lastIndexOf: __arrayProto.lastIndexOf || function (obj, start)
- {
- // Bit cheeky...
- return this.indexOf.apply( this.toArray.reverse(), arguments );
- },
-
+ lastIndexOf: __arrayProto.lastIndexOf,
length: 0,
-
map: function ( fn )
{
- var a = [];
-
- if ( __arrayProto.map ) {
- a = __arrayProto.map.call( this, fn, this );
- }
- else {
- // Compatibility for browsers without EMCA-252-5 (JS 1.6)
- for ( var i=0, ien=this.length ; i<ien ; i++ ) {
- a.push( fn.call( this, this[i], i ) );
- }
- }
+ var a = __arrayProto.map.call( this, fn, this );
return new _Api( this.context, a );
},
-
pluck: function ( prop )
{
var fn = DataTable.util.get(prop);
@@ -7312,72 +6826,72 @@
pop: __arrayProto.pop,
-
push: __arrayProto.push,
+ reduce: __arrayProto.reduce,
- // Does not return an API instance
- reduce: __arrayProto.reduce || function ( fn, init )
- {
- return _fnReduce( this, fn, init, 0, this.length, 1 );
- },
-
-
- reduceRight: __arrayProto.reduceRight || function ( fn, init )
- {
- return _fnReduce( this, fn, init, this.length-1, -1, -1 );
- },
-
+ reduceRight: __arrayProto.reduceRight,
reverse: __arrayProto.reverse,
-
// Object with rows, columns and opts
selector: null,
-
shift: __arrayProto.shift,
-
slice: function () {
return new _Api( this.context, this );
},
-
- sort: __arrayProto.sort, // ? name - order?
-
+ sort: __arrayProto.sort,
splice: __arrayProto.splice,
-
toArray: function ()
{
return __arrayProto.slice.call( this );
},
-
to$: function ()
{
return $( this );
},
-
toJQuery: function ()
{
return $( this );
},
-
unique: function ()
{
- return new _Api( this.context, _unique(this) );
+ return new _Api( this.context, _unique(this.toArray()) );
},
-
unshift: __arrayProto.unshift
} );
+ function _api_scope( scope, fn, struc ) {
+ return function () {
+ var ret = fn.apply( scope || this, arguments );
+
+ // Method extension
+ _Api.extend( ret, ret, struc.methodExt );
+ return ret;
+ };
+ }
+
+ function _api_find( src, name ) {
+ for ( var i=0, ien=src.length ; i<ien ; i++ ) {
+ if ( src[i].name === name ) {
+ return src[i];
+ }
+ }
+ return null;
+ }
+
+ window.__apiStruct = __apiStruct;
+
_Api.extend = function ( scope, obj, ext )
{
// Only extend API instances and static properties of the API
@@ -7387,23 +6901,18 @@
var
i, ien,
- struct,
- methodScoping = function ( scope, fn, struc ) {
- return function () {
- var ret = fn.apply( scope, arguments );
-
- // Method extension
- _Api.extend( ret, ret, struc.methodExt );
- return ret;
- };
- };
+ struct;
for ( i=0, ien=ext.length ; i<ien ; i++ ) {
struct = ext[i];
+ if (struct.name === '__proto__') {
+ continue;
+ }
+
// Value
obj[ struct.name ] = struct.type === 'function' ?
- methodScoping( scope, struct.val, struct ) :
+ _api_scope( scope, struct.val, struct ) :
struct.type === 'object' ?
{} :
struct.val;
@@ -7415,17 +6924,6 @@
}
};
-
- // @todo - Is there need for an augment function?
- // _Api.augment = function ( inst, name )
- // {
- // // Find src object in the structure from the name
- // var parts = name.split('.');
-
- // _Api.extend( inst, obj );
- // };
-
-
// [
// {
// name: 'data' -- string - Property name
@@ -7449,6 +6947,7 @@
// }
// ]
+
_Api.register = _api_register = function ( name, val )
{
if ( Array.isArray( name ) ) {
@@ -7464,22 +6963,13 @@
struct = __apiStruct,
key, method;
- var find = function ( src, name ) {
- for ( var i=0, ien=src.length ; i<ien ; i++ ) {
- if ( src[i].name === name ) {
- return src[i];
- }
- }
- return null;
- };
-
for ( i=0, ien=heir.length ; i<ien ; i++ ) {
method = heir[i].indexOf('()') !== -1;
key = method ?
heir[i].replace('()', '') :
heir[i];
- var src = find( struct, key );
+ var src = _api_find( struct, key );
if ( ! src ) {
src = {
name: key,
@@ -7545,9 +7035,17 @@
var __table_selector = function ( selector, a )
{
if ( Array.isArray(selector) ) {
- return $.map( selector, function (item) {
- return __table_selector(item, a);
- } );
+ var result = [];
+
+ selector.forEach(function (sel) {
+ var inner = __table_selector(sel, a);
+
+ result.push.apply(result, inner);
+ });
+
+ return result.filter( function (item) {
+ return item;
+ });
}
// Integer is used to pick out a table by index
@@ -7556,15 +7054,15 @@
}
// Perform a jQuery selector on the table nodes
- var nodes = $.map( a, function (el, i) {
+ var nodes = a.map( function (el) {
return el.nTable;
} );
return $(nodes)
.filter( selector )
- .map( function (i) {
+ .map( function () {
// Need to translate back from the table node to the settings
- var idx = $.inArray( this, nodes );
+ var idx = nodes.indexOf(this);
return a[ idx ];
} )
.toArray();
@@ -7601,41 +7099,107 @@
tables;
} );
+ // Common methods, combined to reduce size
+ [
+ ['nodes', 'node', 'nTable'],
+ ['body', 'body', 'nTBody'],
+ ['header', 'header', 'nTHead'],
+ ['footer', 'footer', 'nTFoot'],
+ ].forEach(function (item) {
+ _api_registerPlural(
+ 'tables().' + item[0] + '()',
+ 'table().' + item[1] + '()' ,
+ function () {
+ return this.iterator( 'table', function ( ctx ) {
+ return ctx[item[2]];
+ }, 1 );
+ }
+ );
+ });
- _api_registerPlural( 'tables().nodes()', 'table().node()' , function () {
- return this.iterator( 'table', function ( ctx ) {
- return ctx.nTable;
- }, 1 );
- } );
+ // Structure methods
+ [
+ ['header', 'aoHeader'],
+ ['footer', 'aoFooter'],
+ ].forEach(function (item) {
+ _api_register( 'table().' + item[0] + '.structure()' , function (selector) {
+ var indexes = this.columns(selector).indexes().flatten();
+ var ctx = this.context[0];
+
+ return _fnHeaderLayout(ctx, ctx[item[1]], indexes);
+ } );
+ })
- _api_registerPlural( 'tables().body()', 'table().body()' , function () {
+ _api_registerPlural( 'tables().containers()', 'table().container()' , function () {
return this.iterator( 'table', function ( ctx ) {
- return ctx.nTBody;
+ return ctx.nTableWrapper;
}, 1 );
} );
+ _api_register( 'tables().every()', function ( fn ) {
+ var that = this;
- _api_registerPlural( 'tables().header()', 'table().header()' , function () {
- return this.iterator( 'table', function ( ctx ) {
- return ctx.nTHead;
- }, 1 );
- } );
+ return this.iterator('table', function (s, i) {
+ fn.call(that.table(i), i);
+ });
+ });
+ _api_register( 'caption()', function ( value, side ) {
+ var context = this.context;
- _api_registerPlural( 'tables().footer()', 'table().footer()' , function () {
- return this.iterator( 'table', function ( ctx ) {
- return ctx.nTFoot;
- }, 1 );
- } );
+ // Getter - return existing node's content
+ if ( value === undefined ) {
+ var caption = context[0].captionNode;
+ return caption && context.length ?
+ caption.innerHTML :
+ null;
+ }
- _api_registerPlural( 'tables().containers()', 'table().container()' , function () {
return this.iterator( 'table', function ( ctx ) {
- return ctx.nTableWrapper;
+ var table = $(ctx.nTable);
+ var caption = $(ctx.captionNode);
+ var container = $(ctx.nTableWrapper);
+
+ // Create the node if it doesn't exist yet
+ if ( ! caption.length ) {
+ caption = $('<caption/>').html( value );
+ ctx.captionNode = caption[0];
+
+ // If side isn't set, we need to insert into the document to let the
+ // CSS decide so we can read it back, otherwise there is no way to
+ // know if the CSS would put it top or bottom for scrolling
+ if (! side) {
+ table.prepend(caption);
+
+ side = caption.css('caption-side');
+ }
+ }
+
+ caption.html( value );
+
+ if ( side ) {
+ caption.css( 'caption-side', side );
+ caption[0]._captionSide = side;
+ }
+
+ if (container.find('div.dataTables_scroll').length) {
+ var selector = (side === 'top' ? 'Head' : 'Foot');
+
+ container.find('div.dataTables_scroll'+ selector +' table').prepend(caption);
+ }
+ else {
+ table.prepend(caption);
+ }
}, 1 );
} );
+ _api_register( 'caption.node()', function () {
+ var ctx = this.context;
+
+ return ctx.length ? ctx[0].captionNode : null;
+ } );
/**
@@ -7709,7 +7273,7 @@
* * `recordsDisplay` - Data set length once the current filtering criterion
* are applied.
*/
- _api_register( 'page.info()', function ( action ) {
+ _api_register( 'page.info()', function () {
if ( this.context.length === 0 ) {
return undefined;
}
@@ -7786,7 +7350,7 @@
}
// Trigger xhr
- _fnBuildAjax( settings, [], function( json ) {
+ _fnBuildAjax( settings, {}, function( json ) {
_fnClearTable( settings );
var data = _fnAjaxDataSrc( settings, json );
@@ -7795,6 +7359,7 @@
}
_fnReDraw( settings, holdPosition );
+ _fnInitComplete( settings );
_fnProcessingDisplay( settings, false );
} );
}
@@ -7871,11 +7436,9 @@
}
ctx = ctx[0];
- return ctx.ajax ?
- $.isPlainObject( ctx.ajax ) ?
- ctx.ajax.url :
- ctx.ajax :
- ctx.sAjaxSource;
+ return $.isPlainObject( ctx.ajax ) ?
+ ctx.ajax.url :
+ ctx.ajax;
}
// set
@@ -7886,9 +7449,6 @@
else {
settings.ajax = url;
}
- // No need to consider sAjaxSource here since DataTables gives priority
- // to `ajax` over `sAjaxSource`. So setting `ajax` here, renders any
- // value of `sAjaxSource` redundant.
} );
} );
@@ -7928,13 +7488,18 @@
for ( i=0, ien=selector.length ; i<ien ; i++ ) {
// Only split on simple strings - complex expressions will be jQuery selectors
- a = selector[i] && selector[i].split && ! selector[i].match(/[\[\(:]/) ?
+ a = selector[i] && selector[i].split && ! selector[i].match(/[[(:]/) ?
selector[i].split(',') :
[ selector[i] ];
for ( j=0, jen=a.length ; j<jen ; j++ ) {
res = selectFn( typeof a[j] === 'string' ? (a[j]).trim() : a[j] );
+ // Remove empty items
+ res = res.filter( function (item) {
+ return item !== null && item !== undefined;
+ });
+
if ( res && res.length ) {
out = out.concat( res );
}
@@ -7973,24 +7538,24 @@
};
- var _selector_first = function ( inst )
+ // Reduce the API instance to the first item found
+ var _selector_first = function ( old )
{
- // Reduce the API instance to the first item found
- for ( var i=0, ien=inst.length ; i<ien ; i++ ) {
- if ( inst[i].length > 0 ) {
- // Assign the first element to the first item in the instance
- // and truncate the instance and context
- inst[0] = inst[i];
- inst[0].length = 1;
- inst.length = 1;
- inst.context = [ inst.context[i] ];
+ let inst = new _Api(old.context[0]);
- return inst;
- }
+ // Use a push rather than passing to the constructor, since it will
+ // merge arrays down automatically, which isn't what is wanted here
+ if (old.length) {
+ inst.push( old[0] );
+ }
+
+ inst.selector = old.selector;
+
+ // Limit to a single row / column / cell
+ if (inst.length && inst[0].length > 1) {
+ inst[0].splice(1);
}
- // Not found - return an empty instance
- inst.length = 0;
return inst;
};
@@ -8016,7 +7581,8 @@
[] :
_range( 0, displayMaster.length );
}
- else if ( page == 'current' ) {
+
+ if ( page == 'current' ) {
// Current page implies that order=current and filter=applied, since it is
// fairly senseless otherwise, regardless of what order and search actually
// are
@@ -8035,24 +7601,28 @@
// O(n+m) solution by creating a hash map
var displayFilteredMap = {};
- for ( var i=0, ien=displayFiltered.length ; i<ien ; i++ ) {
+ for ( i=0, ien=displayFiltered.length ; i<ien ; i++ ) {
displayFilteredMap[displayFiltered[i]] = null;
}
- a = $.map( displayMaster, function (el) {
- return ! displayFilteredMap.hasOwnProperty(el) ?
- el :
- null;
- } );
+ displayMaster.forEach(function (item) {
+ if (! Object.prototype.hasOwnProperty.call(displayFilteredMap, item)) {
+ a.push(item);
+ }
+ });
}
}
else if ( order == 'index' || order == 'original' ) {
for ( i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
+ if (! settings.aoData[i]) {
+ continue;
+ }
+
if ( search == 'none' ) {
a.push( i );
}
else { // applied | removed
- tmp = $.inArray( i, displayFiltered );
+ tmp = displayFiltered.indexOf(i);
if ((tmp === -1 && search == 'removed') ||
(tmp >= 0 && search == 'applied') )
@@ -8062,6 +7632,25 @@
}
}
}
+ else if ( typeof order === 'number' ) {
+ // Order the rows by the given column
+ var ordered = _fnSort(settings, order, 'asc');
+
+ if (search === 'none') {
+ a = ordered;
+ }
+ else { // applied | removed
+ for (i=0; i<ordered.length; i++) {
+ tmp = displayFiltered.indexOf(ordered[i]);
+
+ if ((tmp === -1 && search == 'removed') ||
+ (tmp >= 0 && search == 'applied') )
+ {
+ a.push( ordered[i] );
+ }
+ }
+ }
+ }
return a;
};
@@ -8082,7 +7671,6 @@
var rows;
var run = function ( sel ) {
var selInt = _intVal( sel );
- var i, ien;
var aoData = settings.aoData;
// Short cut - selector is a number and no options provided (default is
@@ -8096,7 +7684,7 @@
rows = _selector_row_indexes( settings, opts );
}
- if ( selInt !== null && $.inArray( selInt, rows ) !== -1 ) {
+ if ( selInt !== null && rows.indexOf(selInt) !== -1 ) {
// Selector - integer
return [ selInt ];
}
@@ -8107,7 +7695,7 @@
// Selector - function
if ( typeof sel === 'function' ) {
- return $.map( rows, function (idx) {
+ return rows.map( function (idx) {
var row = aoData[ idx ];
return sel( idx, row._aData, row.nTr ) ? idx : null;
} );
@@ -8173,7 +7761,13 @@
.toArray();
};
- return _selector_run( 'row', selector, run, settings, opts );
+ var matched = _selector_run( 'row', selector, run, settings, opts );
+
+ if (opts.order === 'current' || opts.order === 'applied') {
+ _fnSortDisplay(settings, matched);
+ }
+
+ return matched;
};
@@ -8247,38 +7841,15 @@
} );
_api_registerPlural( 'rows().remove()', 'row().remove()', function () {
- var that = this;
-
- this.iterator( 'row', function ( settings, row, thatIdx ) {
+ this.iterator( 'row', function ( settings, row ) {
var data = settings.aoData;
var rowData = data[ row ];
- var i, ien, j, jen;
- var loopRow, loopCells;
-
- data.splice( row, 1 );
-
- // Update the cached indexes
- for ( i=0, ien=data.length ; i<ien ; i++ ) {
- loopRow = data[i];
- loopCells = loopRow.anCells;
-
- // Rows
- if ( loopRow.nTr !== null ) {
- loopRow.nTr._DT_RowIndex = i;
- }
-
- // Cells
- if ( loopCells !== null ) {
- for ( j=0, jen=loopCells.length ; j<jen ; j++ ) {
- loopCells[j]._DT_CellIndex.row = i;
- }
- }
- }
// Delete from the display arrays
- _fnDeleteIndex( settings.aiDisplayMaster, row );
- _fnDeleteIndex( settings.aiDisplay, row );
- _fnDeleteIndex( that[ thatIdx ], row, false ); // maintain local indexes
+ var idx = settings.aiDisplayMaster.indexOf(row);
+ if (idx !== -1) {
+ settings.aiDisplayMaster.splice(idx, 1);
+ }
// For server-side processing tables - subtract the deleted row from the count
if ( settings._iRecordsDisplay > 0 ) {
@@ -8293,12 +7864,8 @@
if ( id !== undefined ) {
delete settings.aIds[ id ];
}
- } );
- this.iterator( 'table', function ( settings ) {
- for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
- settings.aoData[i].idx = i;
- }
+ data[row] = null;
} );
return this;
@@ -8327,7 +7894,7 @@
// Return an Api.rows() extended instance, so rows().nodes() etc can be used
var modRows = this.rows( -1 );
modRows.pop();
- $.merge( modRows, newRows );
+ modRows.push.apply(modRows, newRows);
return modRows;
} );
@@ -8349,7 +7916,7 @@
if ( data === undefined ) {
// Get
- return ctx.length && this.length ?
+ return ctx.length && this.length && this[0].length ?
ctx[0].aoData[ this[0] ]._aData :
undefined;
}
@@ -8373,9 +7940,15 @@
_api_register( 'row().node()', function () {
var ctx = this.context;
- return ctx.length && this.length ?
- ctx[0].aoData[ this[0] ].nTr || null :
- null;
+ if (ctx.length && this.length && this[0].length) {
+ var row = ctx[0].aoData[ this[0] ];
+
+ if (row && row.nTr) {
+ return row.nTr;
+ }
+ }
+
+ return null;
} );
@@ -8400,42 +7973,49 @@
$(document).on('plugin-init.dt', function (e, context) {
var api = new _Api( context );
- var namespace = 'on-plugin-init';
- var stateSaveParamsEvent = 'stateSaveParams.' + namespace;
- var destroyEvent = 'destroy. ' + namespace;
- api.on( stateSaveParamsEvent, function ( e, settings, d ) {
+ api.on( 'stateSaveParams.DT', function ( e, settings, d ) {
// This could be more compact with the API, but it is a lot faster as a simple
// internal loop
var idFn = settings.rowIdFn;
- var data = settings.aoData;
+ var rows = settings.aiDisplayMaster;
var ids = [];
- for (var i=0 ; i<data.length ; i++) {
- if (data[i]._detailsShow) {
- ids.push( '#' + idFn(data[i]._aData) );
+ for (var i=0 ; i<rows.length ; i++) {
+ var rowIdx = rows[i];
+ var data = settings.aoData[rowIdx];
+
+ if (data._detailsShow) {
+ ids.push( '#' + idFn(data._aData) );
}
}
d.childRows = ids;
});
- api.on( destroyEvent, function () {
- api.off(stateSaveParamsEvent + ' ' + destroyEvent);
+ // For future state loads (e.g. with StateRestore)
+ api.on( 'stateLoaded.DT', function (e, settings, state) {
+ __details_state_load( api, state );
});
- var loaded = api.state.loaded();
+ // And the initial load state
+ __details_state_load( api, api.state.loaded() );
+ });
- if ( loaded && loaded.childRows ) {
+ var __details_state_load = function (api, state)
+ {
+ if ( state && state.childRows ) {
api
- .rows( $.map(loaded.childRows, function (id){
- return id.replace(/:/g, '\\:')
+ .rows( state.childRows.map(function (id) {
+ // Escape any `:` characters from the row id. Accounts for
+ // already escaped characters.
+ return id.replace(/([^:\\]*(?:\\.[^:\\]*)*):/g, "$1\\:");
}) )
.every( function () {
- _fnCallbackFire( context, null, 'requestChild', [ this ] )
+ _fnCallbackFire( api.settings()[0], null, 'requestChild', [ this ] )
});
}
- });
+ }
var __details_add = function ( ctx, row, data, klass )
{
@@ -8453,15 +8033,18 @@
// If we get a TR element, then just add it directly - up to the dev
// to add the correct number of columns etc
if ( r.nodeName && r.nodeName.toLowerCase() === 'tr' ) {
+ r.setAttribute( 'data-dt-row', row.idx );
rows.push( r );
}
else {
// Otherwise create a row with a wrapper
- var created = $('<tr><td></td></tr>').addClass( k );
+ var created = $('<tr><td></td></tr>')
+ .attr( 'data-dt-row', row.idx )
+ .addClass( k );
+
$('td', created)
.addClass( k )
- .html( r )
- [0].colSpan = _fnVisbleColumns( ctx );
+ .html( r )[0].colSpan = _fnVisbleColumns( ctx );
rows.push( created[0] );
}
@@ -8566,7 +8149,7 @@
} );
// Column visibility change - update the colspan
- api.on( colvisEvent, function ( e, ctx, idx, vis ) {
+ api.on( colvisEvent, function ( e, ctx ) {
if ( settings !== ctx ) {
return;
}
@@ -8578,8 +8161,14 @@
for ( var i=0, ien=data.length ; i<ien ; i++ ) {
row = data[i];
- if ( row._details ) {
- row._details.children('td[colspan]').attr('colspan', visible );
+ if ( row && row._details ) {
+ row._details.each(function () {
+ var el = $(this).children('td');
+
+ if (el.length == 1) {
+ el.attr('colspan', visible);
+ }
+ });
}
}
} );
@@ -8591,7 +8180,7 @@
}
for ( var i=0, ien=data.length ; i<ien ; i++ ) {
- if ( data[i]._details ) {
+ if ( data[i] && data[i]._details ) {
__details_remove( api, i );
}
}
@@ -8613,9 +8202,9 @@
if ( data === undefined ) {
// get
- return ctx.length && this.length ?
- ctx[0].aoData[ this[0] ]._details :
- undefined;
+ return ctx.length && this.length && ctx[0].aoData[ this[0] ]
+ ? ctx[0].aoData[ this[0] ]._details
+ : undefined;
}
else if ( data === true ) {
// show
@@ -8637,7 +8226,7 @@
_api_register( [
_child_obj+'.show()',
_child_mth+'.show()' // only when `child()` was called with parameters (without
- ], function ( show ) { // it returns an object and this method is not executed)
+ ], function () { // it returns an object and this method is not executed)
__details_display( this, true );
return this;
} );
@@ -8664,7 +8253,7 @@
_api_register( _child_obj+'.isShown()', function () {
var ctx = this.context;
- if ( ctx.length && this.length ) {
+ if ( ctx.length && this.length && ctx[0].aoData[ this[0] ] ) {
// _detailsShown as false or undefined will fall through to return false
return ctx[0].aoData[ this[0] ]._detailsShow || false;
}
@@ -8687,27 +8276,40 @@
// can be an array of these items, comma separated list, or an array of comma
// separated lists
- var __re_column_selector = /^([^:]+):(name|visIdx|visible)$/;
+ var __re_column_selector = /^([^:]+)?:(name|title|visIdx|visible)$/;
// r1 and r2 are redundant - but it means that the parameters match for the
// iterator callback in columns().data()
- var __columnData = function ( settings, column, r1, r2, rows ) {
+ var __columnData = function ( settings, column, r1, r2, rows, type ) {
var a = [];
for ( var row=0, ien=rows.length ; row<ien ; row++ ) {
- a.push( _fnGetCellData( settings, rows[row], column ) );
+ a.push( _fnGetCellData( settings, rows[row], column, type ) );
}
return a;
};
+ var __column_header = function ( settings, column, row ) {
+ var header = settings.aoHeader;
+ var target = row !== undefined
+ ? row
+ : settings.bSortCellsTop // legacy support
+ ? 0
+ : header.length - 1;
+
+ return header[target][column].cell;
+ };
+
var __column_selector = function ( settings, selector, opts )
{
var
columns = settings.aoColumns,
names = _pluck( columns, 'sName' ),
- nodes = _pluck( columns, 'nTh' );
-
+ titles = _pluck( columns, 'sTitle' ),
+ cells = DataTable.util.get('[].[].cell')(settings.aoHeader),
+ nodes = _unique( _flatten([], cells) );
+
var run = function ( s ) {
var selInt = _intVal( s );
@@ -8728,13 +8330,13 @@
if ( typeof s === 'function' ) {
var rows = _selector_row_indexes( settings, opts );
- return $.map( columns, function (col, idx) {
+ return columns.map(function (col, idx) {
return s(
idx,
__columnData( settings, idx, 0, 0, rows ),
- nodes[ idx ]
+ __column_header( settings, idx )
) ? idx : null;
- } );
+ });
}
// jQuery or string selector
@@ -8746,24 +8348,37 @@
switch( match[2] ) {
case 'visIdx':
case 'visible':
- var idx = parseInt( match[1], 10 );
- // Visible index given, convert to column index
- if ( idx < 0 ) {
- // Counting from the right
- var visColumns = $.map( columns, function (col,i) {
- return col.bVisible ? i : null;
- } );
- return [ visColumns[ visColumns.length + idx ] ];
+ if (match[1]) {
+ var idx = parseInt( match[1], 10 );
+ // Visible index given, convert to column index
+ if ( idx < 0 ) {
+ // Counting from the right
+ var visColumns = columns.map( function (col,i) {
+ return col.bVisible ? i : null;
+ } );
+ return [ visColumns[ visColumns.length + idx ] ];
+ }
+ // Counting from the left
+ return [ _fnVisibleToColumnIndex( settings, idx ) ];
}
- // Counting from the left
- return [ _fnVisibleToColumnIndex( settings, idx ) ];
+
+ // `:visible` on its own
+ return columns.map( function (col, i) {
+ return col.bVisible ? i : null;
+ } );
case 'name':
// match by name. `names` is column index complete and in order
- return $.map( names, function (name, i) {
+ return names.map( function (name, i) {
return name === match[1] ? i : null;
} );
+ case 'title':
+ // match by column title
+ return titles.map( function (title, i) {
+ return title === match[1] ? i : null;
+ } );
+
default:
return [];
}
@@ -8778,7 +8393,7 @@
var jqResult = $( nodes )
.filter( s )
.map( function () {
- return $.inArray( this, nodes ); // `nodes` is column index complete and in order
+ return _fnColumnsFromHeader( this ); // `nodes` is column index complete and in order
} )
.toArray();
@@ -8803,7 +8418,7 @@
cols = settings.aoColumns,
col = cols[ column ],
data = settings.aoData,
- row, cells, i, ien, tr;
+ cells, i, ien, tr;
// Get
if ( vis === undefined ) {
@@ -8813,21 +8428,23 @@
// Set
// No change
if ( col.bVisible === vis ) {
- return;
+ return false;
}
if ( vis ) {
// Insert column
// Need to decide if we should use appendChild or insertBefore
- var insertBefore = $.inArray( true, _pluck(cols, 'bVisible'), column+1 );
+ var insertBefore = _pluck(cols, 'bVisible').indexOf(true, column+1);
for ( i=0, ien=data.length ; i<ien ; i++ ) {
- tr = data[i].nTr;
- cells = data[i].anCells;
+ if (data[i]) {
+ tr = data[i].nTr;
+ cells = data[i].anCells;
- if ( tr ) {
- // insertBefore can act like appendChild if 2nd arg is null
- tr.insertBefore( cells[ column ], cells[ insertBefore ] || null );
+ if ( tr ) {
+ // insertBefore can act like appendChild if 2nd arg is null
+ tr.insertBefore( cells[ column ], cells[ insertBefore ] || null );
+ }
}
}
}
@@ -8838,6 +8455,10 @@
// Common actions
col.bVisible = vis;
+
+ _colGroup(settings);
+
+ return true;
};
@@ -8864,15 +8485,21 @@
return inst;
} );
- _api_registerPlural( 'columns().header()', 'column().header()', function ( selector, opts ) {
- return this.iterator( 'column', function ( settings, column ) {
- return settings.aoColumns[column].nTh;
+ _api_registerPlural( 'columns().header()', 'column().header()', function ( row ) {
+ return this.iterator( 'column', function (settings, column) {
+ return __column_header(settings, column, row);
}, 1 );
} );
- _api_registerPlural( 'columns().footer()', 'column().footer()', function ( selector, opts ) {
+ _api_registerPlural( 'columns().footer()', 'column().footer()', function ( row ) {
return this.iterator( 'column', function ( settings, column ) {
- return settings.aoColumns[column].nTf;
+ var footer = settings.aoFooter;
+
+ if (! footer.length) {
+ return null;
+ }
+
+ return settings.aoFooter[row !== undefined ? row : 0][column].cell;
}, 1 );
} );
@@ -8880,6 +8507,12 @@
return this.iterator( 'column-rows', __columnData, 1 );
} );
+ _api_registerPlural( 'columns().render()', 'column().render()', function ( type ) {
+ return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) {
+ return __columnData( settings, column, i, j, rows, type );
+ }, 1 );
+ } );
+
_api_registerPlural( 'columns().dataSrc()', 'column().dataSrc()', function () {
return this.iterator( 'column', function ( settings, column ) {
return settings.aoColumns[column].mData;
@@ -8894,19 +8527,63 @@
}, 1 );
} );
+ _api_registerPlural( 'columns().init()', 'column().init()', function () {
+ return this.iterator( 'column', function ( settings, column ) {
+ return settings.aoColumns[column];
+ }, 1 );
+ } );
+
_api_registerPlural( 'columns().nodes()', 'column().nodes()', function () {
return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) {
return _pluck_order( settings.aoData, rows, 'anCells', column ) ;
}, 1 );
} );
+ _api_registerPlural( 'columns().titles()', 'column().title()', function (title, row) {
+ return this.iterator( 'column', function ( settings, column ) {
+ // Argument shifting
+ if (typeof title === 'number') {
+ row = title;
+ title = undefined;
+ }
+
+ var span = $('span.dt-column-title', this.column(column).header(row));
+
+ if (title !== undefined) {
+ span.html(title);
+ return this;
+ }
+
+ return span.html();
+ }, 1 );
+ } );
+
+ _api_registerPlural( 'columns().types()', 'column().type()', function () {
+ return this.iterator( 'column', function ( settings, column ) {
+ var type = settings.aoColumns[column].sType;
+
+ // If the type was invalidated, then resolve it. This actually does
+ // all columns at the moment. Would only happen once if getting all
+ // column's data types.
+ if (! type) {
+ _fnColumnTypes(settings);
+ }
+
+ return type;
+ }, 1 );
+ } );
+
_api_registerPlural( 'columns().visible()', 'column().visible()', function ( vis, calc ) {
var that = this;
+ var changed = [];
var ret = this.iterator( 'column', function ( settings, column ) {
if ( vis === undefined ) {
return settings.aoColumns[ column ].bVisible;
} // else
- __setColumnVis( settings, column, vis );
+
+ if (__setColumnVis( settings, column, vis )) {
+ changed.push(column);
+ }
} );
// Group the column visibility changes
@@ -8926,16 +8603,40 @@
// Second loop once the first is done for events
that.iterator( 'column', function ( settings, column ) {
- _fnCallbackFire( settings, null, 'column-visibility', [settings, column, vis, calc] );
+ if (changed.includes(column)) {
+ _fnCallbackFire( settings, null, 'column-visibility', [settings, column, vis, calc] );
+ }
} );
- if ( calc === undefined || calc ) {
+ if ( changed.length && (calc === undefined || calc) ) {
that.columns.adjust();
}
});
}
- return ret;
+ return ret;
+ } );
+
+ _api_registerPlural( 'columns().widths()', 'column().width()', function () {
+ // Injects a fake row into the table for just a moment so the widths can
+ // be read, regardless of colspan in the header and rows being present in
+ // the body
+ var columns = this.columns(':visible').count();
+ var row = $('<tr>').html('<td>' + Array(columns).join('</td><td>') + '</td>');
+
+ $(this.table().body()).append(row);
+
+ var widths = row.children().map(function () {
+ return $(this).outerWidth();
+ });
+
+ row.remove();
+
+ return this.iterator( 'column', function ( settings, column ) {
+ var visIdx = _fnColumnIndexToVisible( settings, column );
+
+ return visIdx !== null ? widths[visIdx] : 0;
+ }, 1);
} );
_api_registerPlural( 'columns().indexes()', 'column().index()', function ( type ) {
@@ -9016,7 +8717,7 @@
// Selector - index
if ( $.isPlainObject( s ) ) {
// Valid cell index and its in the array of selectable rows
- return s.column !== undefined && s.row !== undefined && $.inArray( s.row, rows ) !== -1 ?
+ return s.column !== undefined && s.row !== undefined && rows.indexOf(s.row) !== -1 ?
[s] :
[];
}
@@ -9028,7 +8729,7 @@
return { // use a new object, in case someone changes the values
row: el._DT_CellIndex.row,
column: el._DT_CellIndex.column
- };
+ };
} )
.toArray();
@@ -9233,6 +8934,7 @@
*/
_api_register( 'order()', function ( order, dir ) {
var ctx = this.context;
+ var args = Array.prototype.slice.call( arguments );
if ( order === undefined ) {
// get
@@ -9246,14 +8948,14 @@
// Simple column / direction passed in
order = [ [ order, dir ] ];
}
- else if ( order.length && ! Array.isArray( order[0] ) ) {
+ else if ( args.length > 1 ) {
// Arguments passed in (list of 1D arrays)
- order = Array.prototype.slice.call( arguments );
+ order = args;
}
// otherwise a 2D array was passed in
return this.iterator( 'table', function ( settings ) {
- settings.aaSorting = order.slice();
+ settings.aaSorting = Array.isArray(order) ? order.slice() : order;
} );
} );
@@ -9270,7 +8972,7 @@
*/
_api_register( 'order.listener()', function ( node, column, callback ) {
return this.iterator( 'table', function ( settings ) {
- _fnSortAttachListener( settings, node, column, callback );
+ _fnSortAttachListener(settings, node, {}, column, callback);
} );
} );
@@ -9300,18 +9002,45 @@
], function ( dir ) {
var that = this;
- return this.iterator( 'table', function ( settings, i ) {
- var sort = [];
+ if ( ! dir ) {
+ return this.iterator( 'column', function ( settings, idx ) {
+ var sort = _fnSortFlatten( settings );
- $.each( that[i], function (j, col) {
- sort.push( [ col, dir ] );
+ for ( var i=0, ien=sort.length ; i<ien ; i++ ) {
+ if ( sort[i].col === idx ) {
+ return sort[i].dir;
+ }
+ }
+
+ return null;
+ }, 1 );
+ }
+ else {
+ return this.iterator( 'table', function ( settings, i ) {
+ settings.aaSorting = that[i].map( function (col) {
+ return [ col, dir ];
+ } );
} );
+ }
+ } );
- settings.aaSorting = sort;
- } );
+ _api_registerPlural('columns().orderable()', 'column().orderable()', function ( directions ) {
+ return this.iterator( 'column', function ( settings, idx ) {
+ var col = settings.aoColumns[idx];
+
+ return directions ?
+ col.asSorting :
+ col.bSortable;
+ }, 1 );
} );
+ _api_register( 'processing()', function ( show ) {
+ return this.iterator( 'table', function ( ctx ) {
+ _fnProcessingDisplay( ctx, show );
+ } );
+ } );
+
_api_register( 'search()', function ( input, regex, smart, caseInsen ) {
var ctx = this.context;
@@ -9319,7 +9048,7 @@
if ( input === undefined ) {
// get
return ctx.length !== 0 ?
- ctx[0].oPreviousSearch.sSearch :
+ ctx[0].oPreviousSearch.search :
undefined;
}
@@ -9329,15 +9058,48 @@
return;
}
- _fnFilterComplete( settings, $.extend( {}, settings.oPreviousSearch, {
- "sSearch": input+"",
- "bRegex": regex === null ? false : regex,
- "bSmart": smart === null ? true : smart,
- "bCaseInsensitive": caseInsen === null ? true : caseInsen
- } ), 1 );
+ if (typeof regex === 'object') {
+ // New style options to pass to the search builder
+ _fnFilterComplete( settings, $.extend( settings.oPreviousSearch, regex, {
+ search: input
+ } ) );
+ }
+ else {
+ // Compat for the old options
+ _fnFilterComplete( settings, $.extend( settings.oPreviousSearch, {
+ search: input,
+ regex: regex === null ? false : regex,
+ smart: smart === null ? true : smart,
+ caseInsensitive: caseInsen === null ? true : caseInsen
+ } ) );
+ }
} );
} );
+ _api_register( 'search.fixed()', function ( name, search ) {
+ var ret = this.iterator( true, 'table', function ( settings ) {
+ var fixed = settings.searchFixed;
+
+ if (! name) {
+ return Object.keys(fixed)
+ }
+ else if (search === undefined) {
+ return fixed[name];
+ }
+ else if (search === null) {
+ delete fixed[name];
+ }
+ else {
+ fixed[name] = search;
+ }
+
+ return this;
+ } );
+
+ return name !== undefined && search === undefined
+ ? ret[0]
+ : ret;
+ } );
_api_registerPlural(
'columns().search()',
@@ -9348,7 +9110,7 @@
if ( input === undefined ) {
// get
- return preSearch[ column ].sSearch;
+ return preSearch[ column ].search;
}
// set
@@ -9356,26 +9118,78 @@
return;
}
- $.extend( preSearch[ column ], {
- "sSearch": input+"",
- "bRegex": regex === null ? false : regex,
- "bSmart": smart === null ? true : smart,
- "bCaseInsensitive": caseInsen === null ? true : caseInsen
- } );
+ if (typeof regex === 'object') {
+ // New style options to pass to the search builder
+ $.extend( preSearch[ column ], regex, {
+ search: input
+ } );
+ }
+ else {
+ // Old style (with not all options available)
+ $.extend( preSearch[ column ], {
+ search: input,
+ regex: regex === null ? false : regex,
+ smart: smart === null ? true : smart,
+ caseInsensitive: caseInsen === null ? true : caseInsen
+ } );
+ }
- _fnFilterComplete( settings, settings.oPreviousSearch, 1 );
+ _fnFilterComplete( settings, settings.oPreviousSearch );
} );
}
);
+ _api_register([
+ 'columns().search.fixed()',
+ 'column().search.fixed()'
+ ],
+ function ( name, search ) {
+ var ret = this.iterator( true, 'column', function ( settings, colIdx ) {
+ var fixed = settings.aoColumns[colIdx].searchFixed;
+
+ if (! name) {
+ return Object.keys(fixed)
+ }
+ else if (search === undefined) {
+ return fixed[name];
+ }
+ else if (search === null) {
+ delete fixed[name];
+ }
+ else {
+ fixed[name] = search;
+ }
+
+ return this;
+ } );
+
+ return name !== undefined && search === undefined
+ ? ret[0]
+ : ret;
+ }
+ );
/*
* State API methods
*/
- _api_register( 'state()', function () {
- return this.context.length ?
- this.context[0].oSavedState :
- null;
+ _api_register( 'state()', function ( set, ignoreTime ) {
+ // getter
+ if ( ! set ) {
+ return this.context.length ?
+ this.context[0].oSavedState :
+ null;
+ }
+
+ var setMutate = $.extend( true, {}, set );
+
+ // setter
+ return this.iterator( 'table', function ( settings ) {
+ if ( ignoreTime !== false ) {
+ setMutate.time = +new Date() + 100;
+ }
+
+ _fnImplementState( settings, setMutate, function(){} );
+ } );
} );
@@ -9400,8 +9214,6 @@
} );
} );
-
-
/**
* Set the jQuery or window object to be used by DataTables
*
@@ -9454,6 +9266,8 @@
*
* @param {string} version Version string to check for, in the format "X.Y.Z".
* Note that the formats "X" and "X.Y" are also acceptable.
+ * @param {string} [version2=current DataTables version] As above, but optional.
+ * If not given the current DataTables version will be used.
* @returns {boolean} true if this version of DataTables is greater or equal to
* the required version, or false if this version of DataTales is not
* suitable
@@ -9463,9 +9277,11 @@
* @example
* alert( $.fn.dataTable.versionCheck( '1.9.0' ) );
*/
- DataTable.versionCheck = DataTable.fnVersionCheck = function( version )
+ DataTable.versionCheck = function( version, version2 )
{
- var aThis = DataTable.version.split('.');
+ var aThis = version2 ?
+ version2.split('.') :
+ DataTable.version.split('.');
var aThat = version.split('.');
var iThis, iThat;
@@ -9501,7 +9317,7 @@
* $('#example').dataTable();
* }
*/
- DataTable.isDataTable = DataTable.fnIsDataTable = function ( table )
+ DataTable.isDataTable = function ( table )
{
var t = $(table).get(0);
var is = false;
@@ -9539,7 +9355,7 @@
* $(table).DataTable().columns.adjust();
* } );
*/
- DataTable.tables = DataTable.fnTables = function ( visible )
+ DataTable.tables = function ( visible )
{
var api = false;
@@ -9548,11 +9364,15 @@
visible = visible.visible;
}
- var a = $.map( DataTable.settings, function (o) {
- if ( !visible || (visible && $(o.nTable).is(':visible')) ) {
+ var a = DataTable.settings
+ .filter( function (o) {
+ return !visible || (visible && $(o.nTable).is(':visible'))
+ ? true
+ : false;
+ } )
+ .map( function (o) {
return o.nTable;
- }
- } );
+ });
return api ?
new _Api( a ) :
@@ -9567,5428 +9387,2929 @@
* parameters.
*
* @param {object} src The model object which holds all parameters that can be
- * mapped.
- * @param {object} user The object to convert from camel case to Hungarian.
- * @param {boolean} force When set to `true`, properties which already have a
- * Hungarian value in the `user` object will be overwritten. Otherwise they
- * won't be.
- */
- DataTable.camelToHungarian = _fnCamelToHungarian;
-
-
-
- /**
- *
- */
- _api_register( '$()', function ( selector, opts ) {
- var
- rows = this.rows( opts ).nodes(), // Get all rows
- jqRows = $(rows);
-
- return $( [].concat(
- jqRows.filter( selector ).toArray(),
- jqRows.find( selector ).toArray()
- ) );
- } );
-
-
- // jQuery functions to operate on the tables
- $.each( [ 'on', 'one', 'off' ], function (i, key) {
- _api_register( key+'()', function ( /* event, handler */ ) {
- var args = Array.prototype.slice.call(arguments);
-
- // Add the `dt` namespace automatically if it isn't already present
- args[0] = $.map( args[0].split( /\s/ ), function ( e ) {
- return ! e.match(/\.dt\b/) ?
- e+'.dt' :
- e;
- } ).join( ' ' );
-
- var inst = $( this.tables().nodes() );
- inst[key].apply( inst, args );
- return this;
- } );
- } );
-
-
- _api_register( 'clear()', function () {
- return this.iterator( 'table', function ( settings ) {
- _fnClearTable( settings );
- } );
- } );
-
-
- _api_register( 'settings()', function () {
- return new _Api( this.context, this.context );
- } );
-
-
- _api_register( 'init()', function () {
- var ctx = this.context;
- return ctx.length ? ctx[0].oInit : null;
- } );
-
-
- _api_register( 'data()', function () {
- return this.iterator( 'table', function ( settings ) {
- return _pluck( settings.aoData, '_aData' );
- } ).flatten();
- } );
-
-
- _api_register( 'destroy()', function ( remove ) {
- remove = remove || false;
-
- return this.iterator( 'table', function ( settings ) {
- var classes = settings.oClasses;
- var table = settings.nTable;
- var tbody = settings.nTBody;
- var thead = settings.nTHead;
- var tfoot = settings.nTFoot;
- var jqTable = $(table);
- var jqTbody = $(tbody);
- var jqWrapper = $(settings.nTableWrapper);
- var rows = $.map( settings.aoData, function (r) { return r.nTr; } );
- var i, ien;
-
- // Flag to note that the table is currently being destroyed - no action
- // should be taken
- settings.bDestroying = true;
-
- // Fire off the destroy callbacks for plug-ins etc
- _fnCallbackFire( settings, "aoDestroyCallback", "destroy", [settings] );
-
- // If not being removed from the document, make all columns visible
- if ( ! remove ) {
- new _Api( settings ).columns().visible( true );
- }
-
- // Blitz all `DT` namespaced events (these are internal events, the
- // lowercase, `dt` events are user subscribed and they are responsible
- // for removing them
- jqWrapper.off('.DT').find(':not(tbody *)').off('.DT');
- $(window).off('.DT-'+settings.sInstance);
-
- // When scrolling we had to break the table up - restore it
- if ( table != thead.parentNode ) {
- jqTable.children('thead').detach();
- jqTable.append( thead );
- }
-
- if ( tfoot && table != tfoot.parentNode ) {
- jqTable.children('tfoot').detach();
- jqTable.append( tfoot );
- }
-
- settings.aaSorting = [];
- settings.aaSortingFixed = [];
- _fnSortingClasses( settings );
-
- $( rows ).removeClass( settings.asStripeClasses.join(' ') );
-
- $('th, td', thead).removeClass( classes.sSortable+' '+
- classes.sSortableAsc+' '+classes.sSortableDesc+' '+classes.sSortableNone
- );
-
- // Add the TR elements back into the table in their original order
- jqTbody.children().detach();
- jqTbody.append( rows );
-
- var orig = settings.nTableWrapper.parentNode;
-
- // Remove the DataTables generated nodes, events and classes
- var removedMethod = remove ? 'remove' : 'detach';
- jqTable[ removedMethod ]();
- jqWrapper[ removedMethod ]();
-
- // If we need to reattach the table to the document
- if ( ! remove && orig ) {
- // insertBefore acts like appendChild if !arg[1]
- orig.insertBefore( table, settings.nTableReinsertBefore );
-
- // Restore the width of the original table - was read from the style property,
- // so we can restore directly to that
- jqTable
- .css( 'width', settings.sDestroyWidth )
- .removeClass( classes.sTable );
-
- // If the were originally stripe classes - then we add them back here.
- // Note this is not fool proof (for example if not all rows had stripe
- // classes - but it's a good effort without getting carried away
- ien = settings.asDestroyStripes.length;
-
- if ( ien ) {
- jqTbody.children().each( function (i) {
- $(this).addClass( settings.asDestroyStripes[i % ien] );
- } );
- }
- }
-
- /* Remove the settings object from the settings array */
- var idx = $.inArray( settings, DataTable.settings );
- if ( idx !== -1 ) {
- DataTable.settings.splice( idx, 1 );
- }
- } );
- } );
-
-
- // Add the `every()` method for rows, columns and cells in a compact form
- $.each( [ 'column', 'row', 'cell' ], function ( i, type ) {
- _api_register( type+'s().every()', function ( fn ) {
- var opts = this.selector.opts;
- var api = this;
-
- return this.iterator( type, function ( settings, arg1, arg2, arg3, arg4 ) {
- // Rows and columns:
- // arg1 - index
- // arg2 - table counter
- // arg3 - loop counter
- // arg4 - undefined
- // Cells:
- // arg1 - row index
- // arg2 - column index
- // arg3 - table counter
- // arg4 - loop counter
- fn.call(
- api[ type ](
- arg1,
- type==='cell' ? arg2 : opts,
- type==='cell' ? opts : undefined
- ),
- arg1, arg2, arg3, arg4
- );
- } );
- } );
- } );
-
-
- // i18n method for extensions to be able to use the language object from the
- // DataTable
- _api_register( 'i18n()', function ( token, def, plural ) {
- var ctx = this.context[0];
- var resolved = _fnGetObjectDataFn( token )( ctx.oLanguage );
-
- if ( resolved === undefined ) {
- resolved = def;
- }
-
- if ( plural !== undefined && $.isPlainObject( resolved ) ) {
- resolved = resolved[ plural ] !== undefined ?
- resolved[ plural ] :
- resolved._;
- }
-
- return typeof resolved === 'string'
- ? resolved.replace( '%d', plural ) // nb: plural might be undefined,
- : resolved;
- } );
- /**
- * Version string for plug-ins to check compatibility. Allowed format is
- * `a.b.c-d` where: a:int, b:int, c:int, d:string(dev|beta|alpha). `d` is used
- * only for non-release builds. See http://semver.org/ for more information.
- * @member
- * @type string
- * @default Version number
+ * mapped.
+ * @param {object} user The object to convert from camel case to Hungarian.
+ * @param {boolean} force When set to `true`, properties which already have a
+ * Hungarian value in the `user` object will be overwritten. Otherwise they
+ * won't be.
*/
- DataTable.version = "1.13.6";
+ DataTable.camelToHungarian = _fnCamelToHungarian;
+
+
/**
- * Private data store, containing all of the settings objects that are
- * created for the tables on a given page.
*
- * Note that the `DataTable.settings` object is aliased to
- * `jQuery.fn.dataTableExt` through which it may be accessed and
- * manipulated, or `jQuery.fn.dataTable.settings`.
- * @member
- * @type array
- * @default []
- * @private
*/
- DataTable.settings = [];
+ _api_register( '$()', function ( selector, opts ) {
+ var
+ rows = this.rows( opts ).nodes(), // Get all rows
+ jqRows = $(rows);
- /**
- * Object models container, for the various models that DataTables has
- * available to it. These models define the objects that are used to hold
- * the active state and configuration of the table.
- * @namespace
- */
- DataTable.models = {};
+ return $( [].concat(
+ jqRows.filter( selector ).toArray(),
+ jqRows.find( selector ).toArray()
+ ) );
+ } );
+ // jQuery functions to operate on the tables
+ $.each( [ 'on', 'one', 'off' ], function (i, key) {
+ _api_register( key+'()', function ( /* event, handler */ ) {
+ var args = Array.prototype.slice.call(arguments);
- /**
- * Template object for the way in which DataTables holds information about
- * search information for the global filter and individual column filters.
- * @namespace
- */
- DataTable.models.oSearch = {
- /**
- * Flag to indicate if the filtering should be case insensitive or not
- * @type boolean
- * @default true
- */
- "bCaseInsensitive": true,
+ // Add the `dt` namespace automatically if it isn't already present
+ args[0] = args[0].split( /\s/ ).map( function ( e ) {
+ return ! e.match(/\.dt\b/) ?
+ e+'.dt' :
+ e;
+ } ).join( ' ' );
- /**
- * Applied search term
- * @type string
- * @default <i>Empty string</i>
- */
- "sSearch": "",
+ var inst = $( this.tables().nodes() );
+ inst[key].apply( inst, args );
+ return this;
+ } );
+ } );
- /**
- * Flag to indicate if the search term should be interpreted as a
- * regular expression (true) or not (false) and therefore and special
- * regex characters escaped.
- * @type boolean
- * @default false
- */
- "bRegex": false,
- /**
- * Flag to indicate if DataTables is to use its smart filtering or not.
- * @type boolean
- * @default true
- */
- "bSmart": true,
+ _api_register( 'clear()', function () {
+ return this.iterator( 'table', function ( settings ) {
+ _fnClearTable( settings );
+ } );
+ } );
- /**
- * Flag to indicate if DataTables should only trigger a search when
- * the return key is pressed.
- * @type boolean
- * @default false
- */
- "return": false
- };
+ _api_register( 'error()', function (msg) {
+ return this.iterator( 'table', function ( settings ) {
+ _fnLog( settings, 0, msg );
+ } );
+ } );
+ _api_register( 'settings()', function () {
+ return new _Api( this.context, this.context );
+ } );
- /**
- * Template object for the way in which DataTables holds information about
- * each individual row. This is the object format used for the settings
- * aoData array.
- * @namespace
- */
- DataTable.models.oRow = {
- /**
- * TR element for the row
- * @type node
- * @default null
- */
- "nTr": null,
- /**
- * Array of TD elements for each row. This is null until the row has been
- * created.
- * @type array nodes
- * @default []
- */
- "anCells": null,
+ _api_register( 'init()', function () {
+ var ctx = this.context;
+ return ctx.length ? ctx[0].oInit : null;
+ } );
- /**
- * Data object from the original data source for the row. This is either
- * an array if using the traditional form of DataTables, or an object if
- * using mData options. The exact type will depend on the passed in
- * data from the data source, or will be an array if using DOM a data
- * source.
- * @type array|object
- * @default []
- */
- "_aData": [],
- /**
- * Sorting data cache - this array is ostensibly the same length as the
- * number of columns (although each index is generated only as it is
- * needed), and holds the data that is used for sorting each column in the
- * row. We do this cache generation at the start of the sort in order that
- * the formatting of the sort data need be done only once for each cell
- * per sort. This array should not be read from or written to by anything
- * other than the master sorting methods.
- * @type array
- * @default null
- * @private
- */
- "_aSortData": null,
+ _api_register( 'data()', function () {
+ return this.iterator( 'table', function ( settings ) {
+ return _pluck( settings.aoData, '_aData' );
+ } ).flatten();
+ } );
- /**
- * Per cell filtering data cache. As per the sort data cache, used to
- * increase the performance of the filtering in DataTables
- * @type array
- * @default null
- * @private
- */
- "_aFilterData": null,
- /**
- * Filtering data cache. This is the same as the cell filtering cache, but
- * in this case a string rather than an array. This is easily computed with
- * a join on `_aFilterData`, but is provided as a cache so the join isn't
- * needed on every search (memory traded for performance)
- * @type array
- * @default null
- * @private
- */
- "_sFilterRow": null,
+ _api_register( 'trigger()', function ( name, args, bubbles ) {
+ return this.iterator( 'table', function ( settings ) {
+ return _fnCallbackFire( settings, null, name, args, bubbles );
+ } ).flatten();
+ } );
- /**
- * Cache of the class name that DataTables has applied to the row, so we
- * can quickly look at this variable rather than needing to do a DOM check
- * on className for the nTr property.
- * @type string
- * @default <i>Empty string</i>
- * @private
- */
- "_sRowStripe": "",
- /**
- * Denote if the original data source was from the DOM, or the data source
- * object. This is used for invalidating data, so DataTables can
- * automatically read data from the original source, unless uninstructed
- * otherwise.
- * @type string
- * @default null
- * @private
- */
- "src": null,
+ _api_register( 'ready()', function ( fn ) {
+ var ctx = this.context;
- /**
- * Index in the aoData array. This saves an indexOf lookup when we have the
- * object, but want to know the index
- * @type integer
- * @default -1
- * @private
- */
- "idx": -1
- };
+ // Get status of first table
+ if (! fn) {
+ return ctx.length
+ ? (ctx[0]._bInitComplete || false)
+ : null;
+ }
+ // Function to run either once the table becomes ready or
+ // immediately if it is already ready.
+ return this.tables().every(function () {
+ if (this.context[0]._bInitComplete) {
+ fn.call(this);
+ }
+ else {
+ this.on('init', function () {
+ fn.call(this);
+ });
+ }
+ } );
+ } );
- /**
- * Template object for the column information object in DataTables. This object
- * is held in the settings aoColumns array and contains all the information that
- * DataTables needs about each individual column.
- *
- * Note that this object is related to {@link DataTable.defaults.column}
- * but this one is the internal data store for DataTables's cache of columns.
- * It should NOT be manipulated outside of DataTables. Any configuration should
- * be done through the initialisation options.
- * @namespace
- */
- DataTable.models.oColumn = {
- /**
- * Column index. This could be worked out on-the-fly with $.inArray, but it
- * is faster to just hold it as a variable
- * @type integer
- * @default null
- */
- "idx": null,
- /**
- * A list of the columns that sorting should occur on when this column
- * is sorted. That this property is an array allows multi-column sorting
- * to be defined for a column (for example first name / last name columns
- * would benefit from this). The values are integers pointing to the
- * columns to be sorted on (typically it will be a single integer pointing
- * at itself, but that doesn't need to be the case).
- * @type array
- */
- "aDataSort": null,
+ _api_register( 'destroy()', function ( remove ) {
+ remove = remove || false;
- /**
- * Define the sorting directions that are applied to the column, in sequence
- * as the column is repeatedly sorted upon - i.e. the first value is used
- * as the sorting direction when the column if first sorted (clicked on).
- * Sort it again (click again) and it will move on to the next index.
- * Repeat until loop.
- * @type array
- */
- "asSorting": null,
+ return this.iterator( 'table', function ( settings ) {
+ var classes = settings.oClasses;
+ var table = settings.nTable;
+ var tbody = settings.nTBody;
+ var thead = settings.nTHead;
+ var tfoot = settings.nTFoot;
+ var jqTable = $(table);
+ var jqTbody = $(tbody);
+ var jqWrapper = $(settings.nTableWrapper);
+ var rows = settings.aoData.map( function (r) { return r ? r.nTr : null; } );
+ var orderClasses = classes.order;
- /**
- * Flag to indicate if the column is searchable, and thus should be included
- * in the filtering or not.
- * @type boolean
- */
- "bSearchable": null,
+ // Flag to note that the table is currently being destroyed - no action
+ // should be taken
+ settings.bDestroying = true;
- /**
- * Flag to indicate if the column is sortable or not.
- * @type boolean
- */
- "bSortable": null,
+ // Fire off the destroy callbacks for plug-ins etc
+ _fnCallbackFire( settings, "aoDestroyCallback", "destroy", [settings], true );
- /**
- * Flag to indicate if the column is currently visible in the table or not
- * @type boolean
- */
- "bVisible": null,
+ // If not being removed from the document, make all columns visible
+ if ( ! remove ) {
+ new _Api( settings ).columns().visible( true );
+ }
- /**
- * Store for manual type assignment using the `column.type` option. This
- * is held in store so we can manipulate the column's `sType` property.
- * @type string
- * @default null
- * @private
- */
- "_sManualType": null,
+ // Blitz all `DT` namespaced events (these are internal events, the
+ // lowercase, `dt` events are user subscribed and they are responsible
+ // for removing them
+ jqWrapper.off('.DT').find(':not(tbody *)').off('.DT');
+ $(window).off('.DT-'+settings.sInstance);
+
+ // When scrolling we had to break the table up - restore it
+ if ( table != thead.parentNode ) {
+ jqTable.children('thead').detach();
+ jqTable.append( thead );
+ }
- /**
- * Flag to indicate if HTML5 data attributes should be used as the data
- * source for filtering or sorting. True is either are.
- * @type boolean
- * @default false
- * @private
- */
- "_bAttrSrc": false,
+ if ( tfoot && table != tfoot.parentNode ) {
+ jqTable.children('tfoot').detach();
+ jqTable.append( tfoot );
+ }
- /**
- * Developer definable function that is called whenever a cell is created (Ajax source,
- * etc) or processed for input (DOM source). This can be used as a compliment to mRender
- * allowing you to modify the DOM element (add background colour for example) when the
- * element is available.
- * @type function
- * @param {element} nTd The TD node that has been created
- * @param {*} sData The Data for the cell
- * @param {array|object} oData The data for the whole row
- * @param {int} iRow The row index for the aoData data store
- * @default null
- */
- "fnCreatedCell": null,
+ settings.colgroup.remove();
- /**
- * Function to get data from a cell in a column. You should <b>never</b>
- * access data directly through _aData internally in DataTables - always use
- * the method attached to this property. It allows mData to function as
- * required. This function is automatically assigned by the column
- * initialisation method
- * @type function
- * @param {array|object} oData The data array/object for the array
- * (i.e. aoData[]._aData)
- * @param {string} sSpecific The specific data type you want to get -
- * 'display', 'type' 'filter' 'sort'
- * @returns {*} The data for the cell from the given row's data
- * @default null
- */
- "fnGetData": null,
+ settings.aaSorting = [];
+ settings.aaSortingFixed = [];
+ _fnSortingClasses( settings );
- /**
- * Function to set data for a cell in the column. You should <b>never</b>
- * set the data directly to _aData internally in DataTables - always use
- * this method. It allows mData to function as required. This function
- * is automatically assigned by the column initialisation method
- * @type function
- * @param {array|object} oData The data array/object for the array
- * (i.e. aoData[]._aData)
- * @param {*} sValue Value to set
- * @default null
- */
- "fnSetData": null,
+ $('th, td', thead)
+ .removeClass(
+ orderClasses.canAsc + ' ' +
+ orderClasses.canDesc + ' ' +
+ orderClasses.isAsc + ' ' +
+ orderClasses.isDesc
+ )
+ .css('width', '');
- /**
- * Property to read the value for the cells in the column from the data
- * source array / object. If null, then the default content is used, if a
- * function is given then the return from the function is used.
- * @type function|int|string|null
- * @default null
- */
- "mData": null,
+ // Add the TR elements back into the table in their original order
+ jqTbody.children().detach();
+ jqTbody.append( rows );
- /**
- * Partner property to mData which is used (only when defined) to get
- * the data - i.e. it is basically the same as mData, but without the
- * 'set' option, and also the data fed to it is the result from mData.
- * This is the rendering method to match the data method of mData.
- * @type function|int|string|null
- * @default null
- */
- "mRender": null,
+ var orig = settings.nTableWrapper.parentNode;
+ var insertBefore = settings.nTableWrapper.nextSibling;
- /**
- * Unique header TH/TD element for this column - this is what the sorting
- * listener is attached to (if sorting is enabled.)
- * @type node
- * @default null
- */
- "nTh": null,
+ // Remove the DataTables generated nodes, events and classes
+ var removedMethod = remove ? 'remove' : 'detach';
+ jqTable[ removedMethod ]();
+ jqWrapper[ removedMethod ]();
- /**
- * Unique footer TH/TD element for this column (if there is one). Not used
- * in DataTables as such, but can be used for plug-ins to reference the
- * footer for each column.
- * @type node
- * @default null
- */
- "nTf": null,
+ // If we need to reattach the table to the document
+ if ( ! remove && orig ) {
+ // insertBefore acts like appendChild if !arg[1]
+ orig.insertBefore( table, insertBefore );
- /**
- * The class to apply to all TD elements in the table's TBODY for the column
- * @type string
- * @default null
- */
- "sClass": null,
+ // Restore the width of the original table - was read from the style property,
+ // so we can restore directly to that
+ jqTable
+ .css( 'width', settings.sDestroyWidth )
+ .removeClass( classes.table );
+ }
- /**
- * When DataTables calculates the column widths to assign to each column,
- * it finds the longest string in each column and then constructs a
- * temporary table and reads the widths from that. The problem with this
- * is that "mmm" is much wider then "iiii", but the latter is a longer
- * string - thus the calculation can go wrong (doing it properly and putting
- * it into an DOM object and measuring that is horribly(!) slow). Thus as
- * a "work around" we provide this option. It will append its value to the
- * text that is found to be the longest string for the column - i.e. padding.
- * @type string
- */
- "sContentPadding": null,
+ /* Remove the settings object from the settings array */
+ var idx = DataTable.settings.indexOf(settings);
+ if ( idx !== -1 ) {
+ DataTable.settings.splice( idx, 1 );
+ }
+ } );
+ } );
- /**
- * Allows a default value to be given for a column's data, and will be used
- * whenever a null data source is encountered (this can be because mData
- * is set to null, or because the data source itself is null).
- * @type string
- * @default null
- */
- "sDefaultContent": null,
- /**
- * Name for the column, allowing reference to the column by name as well as
- * by index (needs a lookup to work by name).
- * @type string
- */
- "sName": null,
+ // Add the `every()` method for rows, columns and cells in a compact form
+ $.each( [ 'column', 'row', 'cell' ], function ( i, type ) {
+ _api_register( type+'s().every()', function ( fn ) {
+ var opts = this.selector.opts;
+ var api = this;
+ var inst;
+ var counter = 0;
- /**
- * Custom sorting data type - defines which of the available plug-ins in
- * afnSortData the custom sorting will use - if any is defined.
- * @type string
- * @default std
- */
- "sSortDataType": 'std',
+ return this.iterator( 'every', function ( settings, selectedIdx, tableIdx ) {
+ inst = api[ type ](selectedIdx, opts);
- /**
- * Class to be applied to the header element when sorting on this column
- * @type string
- * @default null
- */
- "sSortingClass": null,
+ if (type === 'cell') {
+ fn.call(inst, inst[0][0].row, inst[0][0].column, tableIdx, counter);
+ }
+ else {
+ fn.call(inst, selectedIdx, tableIdx, counter);
+ }
- /**
- * Class to be applied to the header element when sorting on this column -
- * when jQuery UI theming is used.
- * @type string
- * @default null
- */
- "sSortingClassJUI": null,
+ counter++;
+ } );
+ } );
+ } );
- /**
- * Title of the column - what is seen in the TH element (nTh).
- * @type string
- */
- "sTitle": null,
- /**
- * Column sorting and filtering type
- * @type string
- * @default null
- */
- "sType": null,
+ // i18n method for extensions to be able to use the language object from the
+ // DataTable
+ _api_register( 'i18n()', function ( token, def, plural ) {
+ var ctx = this.context[0];
+ var resolved = _fnGetObjectDataFn( token )( ctx.oLanguage );
- /**
- * Width of the column
- * @type string
- * @default null
- */
- "sWidth": null,
+ if ( resolved === undefined ) {
+ resolved = def;
+ }
- /**
- * Width of the column when it was first "encountered"
- * @type string
- * @default null
- */
- "sWidthOrig": null
- };
+ if ( $.isPlainObject( resolved ) ) {
+ resolved = plural !== undefined && resolved[ plural ] !== undefined ?
+ resolved[ plural ] :
+ resolved._;
+ }
+ return typeof resolved === 'string'
+ ? resolved.replace( '%d', plural ) // nb: plural might be undefined,
+ : resolved;
+ } );
- /*
- * Developer note: The properties of the object below are given in Hungarian
- * notation, that was used as the interface for DataTables prior to v1.10, however
- * from v1.10 onwards the primary interface is camel case. In order to avoid
- * breaking backwards compatibility utterly with this change, the Hungarian
- * version is still, internally the primary interface, but is is not documented
- * - hence the @name tags in each doc comment. This allows a Javascript function
- * to create a map from Hungarian notation to camel case (going the other direction
- * would require each property to be listed, which would add around 3K to the size
- * of DataTables, while this method is about a 0.5K hit).
+ /**
+ * Version string for plug-ins to check compatibility. Allowed format is
+ * `a.b.c-d` where: a:int, b:int, c:int, d:string(dev|beta|alpha). `d` is used
+ * only for non-release builds. See https://semver.org/ for more information.
+ * @member
+ * @type string
+ * @default Version number
+ */
+ DataTable.version = "2.0.8";
+
+ /**
+ * Private data store, containing all of the settings objects that are
+ * created for the tables on a given page.
*
- * Ultimately this does pave the way for Hungarian notation to be dropped
- * completely, but that is a massive amount of work and will break current
- * installs (therefore is on-hold until v2).
+ * Note that the `DataTable.settings` object is aliased to
+ * `jQuery.fn.dataTableExt` through which it may be accessed and
+ * manipulated, or `jQuery.fn.dataTable.settings`.
+ * @member
+ * @type array
+ * @default []
+ * @private
*/
+ DataTable.settings = [];
/**
- * Initialisation options that can be given to DataTables at initialisation
- * time.
+ * Object models container, for the various models that DataTables has
+ * available to it. These models define the objects that are used to hold
+ * the active state and configuration of the table.
* @namespace
*/
- DataTable.defaults = {
- /**
- * An array of data to use for the table, passed in at initialisation which
- * will be used in preference to any data which is already in the DOM. This is
- * particularly useful for constructing tables purely in Javascript, for
- * example with a custom Ajax call.
- * @type array
- * @default null
- *
- * @dtopt Option
- * @name DataTable.defaults.data
- *
- * @example
- * // Using a 2D array data source
- * $(document).ready( function () {
- * $('#example').dataTable( {
- * "data": [
- * ['Trident', 'Internet Explorer 4.0', 'Win 95+', 4, 'X'],
- * ['Trident', 'Internet Explorer 5.0', 'Win 95+', 5, 'C'],
- * ],
- * "columns": [
- * { "title": "Engine" },
- * { "title": "Browser" },
- * { "title": "Platform" },
- * { "title": "Version" },
- * { "title": "Grade" }
- * ]
- * } );
- * } );
- *
- * @example
- * // Using an array of objects as a data source (`data`)
- * $(document).ready( function () {
- * $('#example').dataTable( {
- * "data": [
- * {
- * "engine": "Trident",
- * "browser": "Internet Explorer 4.0",
- * "platform": "Win 95+",
- * "version": 4,
- * "grade": "X"
- * },
- * {
- * "engine": "Trident",
- * "browser": "Internet Explorer 5.0",
- * "platform": "Win 95+",
- * "version": 5,
- * "grade": "C"
- * }
- * ],
- * "columns": [
- * { "title": "Engine", "data": "engine" },
- * { "title": "Browser", "data": "browser" },
- * { "title": "Platform", "data": "platform" },
- * { "title": "Version", "data": "version" },
- * { "title": "Grade", "data": "grade" }
- * ]
- * } );
- * } );
- */
- "aaData": null,
-
+ DataTable.models = {};
- /**
- * If ordering is enabled, then DataTables will perform a first pass sort on
- * initialisation. You can define which column(s) the sort is performed
- * upon, and the sorting direction, with this variable. The `sorting` array
- * should contain an array for each column to be sorted initially containing
- * the column's index and a direction string ('asc' or 'desc').
- * @type array
- * @default [[0,'asc']]
- *
- * @dtopt Option
- * @name DataTable.defaults.order
- *
- * @example
- * // Sort by 3rd column first, and then 4th column
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "order": [[2,'asc'], [3,'desc']]
- * } );
- * } );
- *
- * // No initial sorting
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "order": []
- * } );
- * } );
- */
- "aaSorting": [[0,'asc']],
- /**
- * This parameter is basically identical to the `sorting` parameter, but
- * cannot be overridden by user interaction with the table. What this means
- * is that you could have a column (visible or hidden) which the sorting
- * will always be forced on first - any sorting after that (from the user)
- * will then be performed as required. This can be useful for grouping rows
- * together.
- * @type array
- * @default null
- *
- * @dtopt Option
- * @name DataTable.defaults.orderFixed
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "orderFixed": [[0,'asc']]
- * } );
- * } )
+ /**
+ * Template object for the way in which DataTables holds information about
+ * search information for the global filter and individual column filters.
+ * @namespace
+ */
+ DataTable.models.oSearch = {
+ /**
+ * Flag to indicate if the filtering should be case insensitive or not
*/
- "aaSortingFixed": [],
+ "caseInsensitive": true,
+ /**
+ * Applied search term
+ */
+ "search": "",
/**
- * DataTables can be instructed to load data to display in the table from a
- * Ajax source. This option defines how that Ajax call is made and where to.
- *
- * The `ajax` property has three different modes of operation, depending on
- * how it is defined. These are:
- *
- * * `string` - Set the URL from where the data should be loaded from.
- * * `object` - Define properties for `jQuery.ajax`.
- * * `function` - Custom data get function
- *
- * `string`
- * --------
- *
- * As a string, the `ajax` property simply defines the URL from which
- * DataTables will load data.
- *
- * `object`
- * --------
- *
- * As an object, the parameters in the object are passed to
- * [jQuery.ajax](http://api.jquery.com/jQuery.ajax/) allowing fine control
- * of the Ajax request. DataTables has a number of default parameters which
- * you can override using this option. Please refer to the jQuery
- * documentation for a full description of the options available, although
- * the following parameters provide additional options in DataTables or
- * require special consideration:
- *
- * * `data` - As with jQuery, `data` can be provided as an object, but it
- * can also be used as a function to manipulate the data DataTables sends
- * to the server. The function takes a single parameter, an object of
- * parameters with the values that DataTables has readied for sending. An
- * object may be returned which will be merged into the DataTables
- * defaults, or you can add the items to the object that was passed in and
- * not return anything from the function. This supersedes `fnServerParams`
- * from DataTables 1.9-.
- *
- * * `dataSrc` - By default DataTables will look for the property `data` (or
- * `aaData` for compatibility with DataTables 1.9-) when obtaining data
- * from an Ajax source or for server-side processing - this parameter
- * allows that property to be changed. You can use Javascript dotted
- * object notation to get a data source for multiple levels of nesting, or
- * it my be used as a function. As a function it takes a single parameter,
- * the JSON returned from the server, which can be manipulated as
- * required, with the returned value being that used by DataTables as the
- * data source for the table. This supersedes `sAjaxDataProp` from
- * DataTables 1.9-.
- *
- * * `success` - Should not be overridden it is used internally in
- * DataTables. To manipulate / transform the data returned by the server
- * use `ajax.dataSrc`, or use `ajax` as a function (see below).
- *
- * `function`
- * ----------
- *
- * As a function, making the Ajax call is left up to yourself allowing
- * complete control of the Ajax request. Indeed, if desired, a method other
- * than Ajax could be used to obtain the required data, such as Web storage
- * or an AIR database.
- *
- * The function is given four parameters and no return is required. The
- * parameters are:
- *
- * 1. _object_ - Data to send to the server
- * 2. _function_ - Callback function that must be executed when the required
- * data has been obtained. That data should be passed into the callback
- * as the only parameter
- * 3. _object_ - DataTables settings object for the table
- *
- * Note that this supersedes `fnServerData` from DataTables 1.9-.
- *
- * @type string|object|function
- * @default null
- *
- * @dtopt Option
- * @name DataTable.defaults.ajax
- * @since 1.10.0
- *
- * @example
- * // Get JSON data from a file via Ajax.
- * // Note DataTables expects data in the form `{ data: [ ...data... ] }` by default).
- * $('#example').dataTable( {
- * "ajax": "data.json"
- * } );
- *
- * @example
- * // Get JSON data from a file via Ajax, using `dataSrc` to change
- * // `data` to `tableData` (i.e. `{ tableData: [ ...data... ] }`)
- * $('#example').dataTable( {
- * "ajax": {
- * "url": "data.json",
- * "dataSrc": "tableData"
- * }
- * } );
- *
- * @example
- * // Get JSON data from a file via Ajax, using `dataSrc` to read data
- * // from a plain array rather than an array in an object
- * $('#example').dataTable( {
- * "ajax": {
- * "url": "data.json",
- * "dataSrc": ""
- * }
- * } );
- *
- * @example
- * // Manipulate the data returned from the server - add a link to data
- * // (note this can, should, be done using `render` for the column - this
- * // is just a simple example of how the data can be manipulated).
- * $('#example').dataTable( {
- * "ajax": {
- * "url": "data.json",
- * "dataSrc": function ( json ) {
- * for ( var i=0, ien=json.length ; i<ien ; i++ ) {
- * json[i][0] = '<a href="/message/'+json[i][0]+'>View message</a>';
- * }
- * return json;
- * }
- * }
- * } );
- *
- * @example
- * // Add data to the request
- * $('#example').dataTable( {
- * "ajax": {
- * "url": "data.json",
- * "data": function ( d ) {
- * return {
- * "extra_search": $('#extra').val()
- * };
- * }
- * }
- * } );
- *
- * @example
- * // Send request as POST
- * $('#example').dataTable( {
- * "ajax": {
- * "url": "data.json",
- * "type": "POST"
- * }
- * } );
- *
- * @example
- * // Get the data from localStorage (could interface with a form for
- * // adding, editing and removing rows).
- * $('#example').dataTable( {
- * "ajax": function (data, callback, settings) {
- * callback(
- * JSON.parse( localStorage.getItem('dataTablesData') )
- * );
- * }
- * } );
+ * Flag to indicate if the search term should be interpreted as a
+ * regular expression (true) or not (false) and therefore and special
+ * regex characters escaped.
*/
- "ajax": null,
+ "regex": false,
+ /**
+ * Flag to indicate if DataTables is to use its smart filtering or not.
+ */
+ "smart": true,
/**
- * This parameter allows you to readily specify the entries in the length drop
- * down menu that DataTables shows when pagination is enabled. It can be
- * either a 1D array of options which will be used for both the displayed
- * option and the value, or a 2D array which will use the array in the first
- * position as the value, and the array in the second position as the
- * displayed options (useful for language strings such as 'All').
- *
- * Note that the `pageLength` property will be automatically set to the
- * first value given in this array, unless `pageLength` is also provided.
- * @type array
- * @default [ 10, 25, 50, 100 ]
- *
- * @dtopt Option
- * @name DataTable.defaults.lengthMenu
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "lengthMenu": [[10, 25, 50, -1], [10, 25, 50, "All"]]
- * } );
- * } );
+ * Flag to indicate if DataTables should only trigger a search when
+ * the return key is pressed.
*/
- "aLengthMenu": [ 10, 25, 50, 100 ],
+ "return": false
+ };
+
+
+ /**
+ * Template object for the way in which DataTables holds information about
+ * each individual row. This is the object format used for the settings
+ * aoData array.
+ * @namespace
+ */
+ DataTable.models.oRow = {
/**
- * The `columns` option in the initialisation parameter allows you to define
- * details about the way individual columns behave. For a full list of
- * column options that can be set, please see
- * {@link DataTable.defaults.column}. Note that if you use `columns` to
- * define your columns, you must have an entry in the array for every single
- * column that you have in your table (these can be null if you don't which
- * to specify any options).
- * @member
- *
- * @name DataTable.defaults.column
+ * TR element for the row
*/
- "aoColumns": null,
+ "nTr": null,
/**
- * Very similar to `columns`, `columnDefs` allows you to target a specific
- * column, multiple columns, or all columns, using the `targets` property of
- * each object in the array. This allows great flexibility when creating
- * tables, as the `columnDefs` arrays can be of any length, targeting the
- * columns you specifically want. `columnDefs` may use any of the column
- * options available: {@link DataTable.defaults.column}, but it _must_
- * have `targets` defined in each object in the array. Values in the `targets`
- * array may be:
- * <ul>
- * <li>a string - class name will be matched on the TH for the column</li>
- * <li>0 or a positive integer - column index counting from the left</li>
- * <li>a negative integer - column index counting from the right</li>
- * <li>the string "_all" - all columns (i.e. assign a default)</li>
- * </ul>
- * @member
- *
- * @name DataTable.defaults.columnDefs
+ * Array of TD elements for each row. This is null until the row has been
+ * created.
*/
- "aoColumnDefs": null,
-
+ "anCells": null,
/**
- * Basically the same as `search`, this parameter defines the individual column
- * filtering state at initialisation time. The array must be of the same size
- * as the number of columns, and each element be an object with the parameters
- * `search` and `escapeRegex` (the latter is optional). 'null' is also
- * accepted and the default will be used.
- * @type array
- * @default []
- *
- * @dtopt Option
- * @name DataTable.defaults.searchCols
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "searchCols": [
- * null,
- * { "search": "My filter" },
- * null,
- * { "search": "^[0-9]", "escapeRegex": false }
- * ]
- * } );
- * } )
+ * Data object from the original data source for the row. This is either
+ * an array if using the traditional form of DataTables, or an object if
+ * using mData options. The exact type will depend on the passed in
+ * data from the data source, or will be an array if using DOM a data
+ * source.
*/
- "aoSearchCols": [],
+ "_aData": [],
+ /**
+ * Sorting data cache - this array is ostensibly the same length as the
+ * number of columns (although each index is generated only as it is
+ * needed), and holds the data that is used for sorting each column in the
+ * row. We do this cache generation at the start of the sort in order that
+ * the formatting of the sort data need be done only once for each cell
+ * per sort. This array should not be read from or written to by anything
+ * other than the master sorting methods.
+ */
+ "_aSortData": null,
/**
- * An array of CSS classes that should be applied to displayed rows. This
- * array may be of any length, and DataTables will apply each class
- * sequentially, looping when required.
- * @type array
- * @default null <i>Will take the values determined by the `oClasses.stripe*`
- * options</i>
- *
- * @dtopt Option
- * @name DataTable.defaults.stripeClasses
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "stripeClasses": [ 'strip1', 'strip2', 'strip3' ]
- * } );
- * } )
+ * Per cell filtering data cache. As per the sort data cache, used to
+ * increase the performance of the filtering in DataTables
*/
- "asStripeClasses": null,
+ "_aFilterData": null,
+ /**
+ * Filtering data cache. This is the same as the cell filtering cache, but
+ * in this case a string rather than an array. This is easily computed with
+ * a join on `_aFilterData`, but is provided as a cache so the join isn't
+ * needed on every search (memory traded for performance)
+ */
+ "_sFilterRow": null,
/**
- * Enable or disable automatic column width calculation. This can be disabled
- * as an optimisation (it takes some time to calculate the widths) if the
- * tables widths are passed in using `columns`.
- * @type boolean
- * @default true
- *
- * @dtopt Features
- * @name DataTable.defaults.autoWidth
- *
- * @example
- * $(document).ready( function () {
- * $('#example').dataTable( {
- * "autoWidth": false
- * } );
- * } );
+ * Denote if the original data source was from the DOM, or the data source
+ * object. This is used for invalidating data, so DataTables can
+ * automatically read data from the original source, unless uninstructed
+ * otherwise.
*/
- "bAutoWidth": true,
+ "src": null,
+ /**
+ * Index in the aoData array. This saves an indexOf lookup when we have the
+ * object, but want to know the index
+ */
+ "idx": -1,
/**
- * Deferred rendering can provide DataTables with a huge speed boost when you
- * are using an Ajax or JS data source for the table. This option, when set to
- * true, will cause DataTables to defer the creation of the table elements for
- * each row until they are needed for a draw - saving a significant amount of
- * time.
- * @type boolean
- * @default false
- *
- * @dtopt Features
- * @name DataTable.defaults.deferRender
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "ajax": "sources/arrays.txt",
- * "deferRender": true
- * } );
- * } );
+ * Cached display value
*/
- "bDeferRender": false,
+ displayData: null
+ };
+ /**
+ * Template object for the column information object in DataTables. This object
+ * is held in the settings aoColumns array and contains all the information that
+ * DataTables needs about each individual column.
+ *
+ * Note that this object is related to {@link DataTable.defaults.column}
+ * but this one is the internal data store for DataTables's cache of columns.
+ * It should NOT be manipulated outside of DataTables. Any configuration should
+ * be done through the initialisation options.
+ * @namespace
+ */
+ DataTable.models.oColumn = {
/**
- * Replace a DataTable which matches the given selector and replace it with
- * one which has the properties of the new initialisation object passed. If no
- * table matches the selector, then the new DataTable will be constructed as
- * per normal.
- * @type boolean
- * @default false
- *
- * @dtopt Options
- * @name DataTable.defaults.destroy
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "srollY": "200px",
- * "paginate": false
- * } );
- *
- * // Some time later....
- * $('#example').dataTable( {
- * "filter": false,
- * "destroy": true
- * } );
- * } );
+ * Column index.
*/
- "bDestroy": false,
-
+ "idx": null,
/**
- * Enable or disable filtering of data. Filtering in DataTables is "smart" in
- * that it allows the end user to input multiple words (space separated) and
- * will match a row containing those words, even if not in the order that was
- * specified (this allow matching across multiple columns). Note that if you
- * wish to use filtering in DataTables this must remain 'true' - to remove the
- * default filtering input box and retain filtering abilities, please use
- * {@link DataTable.defaults.dom}.
- * @type boolean
- * @default true
- *
- * @dtopt Features
- * @name DataTable.defaults.searching
- *
- * @example
- * $(document).ready( function () {
- * $('#example').dataTable( {
- * "searching": false
- * } );
- * } );
+ * A list of the columns that sorting should occur on when this column
+ * is sorted. That this property is an array allows multi-column sorting
+ * to be defined for a column (for example first name / last name columns
+ * would benefit from this). The values are integers pointing to the
+ * columns to be sorted on (typically it will be a single integer pointing
+ * at itself, but that doesn't need to be the case).
*/
- "bFilter": true,
-
+ "aDataSort": null,
/**
- * Enable or disable the table information display. This shows information
- * about the data that is currently visible on the page, including information
- * about filtered data if that action is being performed.
- * @type boolean
- * @default true
- *
- * @dtopt Features
- * @name DataTable.defaults.info
- *
- * @example
- * $(document).ready( function () {
- * $('#example').dataTable( {
- * "info": false
- * } );
- * } );
+ * Define the sorting directions that are applied to the column, in sequence
+ * as the column is repeatedly sorted upon - i.e. the first value is used
+ * as the sorting direction when the column if first sorted (clicked on).
+ * Sort it again (click again) and it will move on to the next index.
+ * Repeat until loop.
*/
- "bInfo": true,
-
+ "asSorting": null,
/**
- * Allows the end user to select the size of a formatted page from a select
- * menu (sizes are 10, 25, 50 and 100). Requires pagination (`paginate`).
- * @type boolean
- * @default true
- *
- * @dtopt Features
- * @name DataTable.defaults.lengthChange
- *
- * @example
- * $(document).ready( function () {
- * $('#example').dataTable( {
- * "lengthChange": false
- * } );
- * } );
+ * Flag to indicate if the column is searchable, and thus should be included
+ * in the filtering or not.
*/
- "bLengthChange": true,
-
+ "bSearchable": null,
/**
- * Enable or disable pagination.
- * @type boolean
- * @default true
- *
- * @dtopt Features
- * @name DataTable.defaults.paging
- *
- * @example
- * $(document).ready( function () {
- * $('#example').dataTable( {
- * "paging": false
- * } );
- * } );
+ * Flag to indicate if the column is sortable or not.
*/
- "bPaginate": true,
-
+ "bSortable": null,
/**
- * Enable or disable the display of a 'processing' indicator when the table is
- * being processed (e.g. a sort). This is particularly useful for tables with
- * large amounts of data where it can take a noticeable amount of time to sort
- * the entries.
- * @type boolean
- * @default false
- *
- * @dtopt Features
- * @name DataTable.defaults.processing
- *
- * @example
- * $(document).ready( function () {
- * $('#example').dataTable( {
- * "processing": true
- * } );
- * } );
+ * Flag to indicate if the column is currently visible in the table or not
*/
- "bProcessing": false,
-
+ "bVisible": null,
/**
- * Retrieve the DataTables object for the given selector. Note that if the
- * table has already been initialised, this parameter will cause DataTables
- * to simply return the object that has already been set up - it will not take
- * account of any changes you might have made to the initialisation object
- * passed to DataTables (setting this parameter to true is an acknowledgement
- * that you understand this). `destroy` can be used to reinitialise a table if
- * you need.
- * @type boolean
- * @default false
- *
- * @dtopt Options
- * @name DataTable.defaults.retrieve
- *
- * @example
- * $(document).ready( function() {
- * initTable();
- * tableActions();
- * } );
- *
- * function initTable ()
- * {
- * return $('#example').dataTable( {
- * "scrollY": "200px",
- * "paginate": false,
- * "retrieve": true
- * } );
- * }
- *
- * function tableActions ()
- * {
- * var table = initTable();
- * // perform API operations with oTable
- * }
+ * Store for manual type assignment using the `column.type` option. This
+ * is held in store so we can manipulate the column's `sType` property.
*/
- "bRetrieve": false,
-
+ "_sManualType": null,
/**
- * When vertical (y) scrolling is enabled, DataTables will force the height of
- * the table's viewport to the given height at all times (useful for layout).
- * However, this can look odd when filtering data down to a small data set,
- * and the footer is left "floating" further down. This parameter (when
- * enabled) will cause DataTables to collapse the table's viewport down when
- * the result set will fit within the given Y height.
- * @type boolean
- * @default false
- *
- * @dtopt Options
- * @name DataTable.defaults.scrollCollapse
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "scrollY": "200",
- * "scrollCollapse": true
- * } );
- * } );
+ * Flag to indicate if HTML5 data attributes should be used as the data
+ * source for filtering or sorting. True is either are.
*/
- "bScrollCollapse": false,
-
+ "_bAttrSrc": false,
/**
- * Configure DataTables to use server-side processing. Note that the
- * `ajax` parameter must also be given in order to give DataTables a
- * source to obtain the required data for each draw.
- * @type boolean
- * @default false
- *
- * @dtopt Features
- * @dtopt Server-side
- * @name DataTable.defaults.serverSide
- *
- * @example
- * $(document).ready( function () {
- * $('#example').dataTable( {
- * "serverSide": true,
- * "ajax": "xhr.php"
- * } );
- * } );
+ * Developer definable function that is called whenever a cell is created (Ajax source,
+ * etc) or processed for input (DOM source). This can be used as a compliment to mRender
+ * allowing you to modify the DOM element (add background colour for example) when the
+ * element is available.
*/
- "bServerSide": false,
-
+ "fnCreatedCell": null,
/**
- * Enable or disable sorting of columns. Sorting of individual columns can be
- * disabled by the `sortable` option for each column.
- * @type boolean
- * @default true
- *
- * @dtopt Features
- * @name DataTable.defaults.ordering
- *
- * @example
- * $(document).ready( function () {
- * $('#example').dataTable( {
- * "ordering": false
- * } );
- * } );
+ * Function to get data from a cell in a column. You should <b>never</b>
+ * access data directly through _aData internally in DataTables - always use
+ * the method attached to this property. It allows mData to function as
+ * required. This function is automatically assigned by the column
+ * initialisation method
*/
- "bSort": true,
-
+ "fnGetData": null,
/**
- * Enable or display DataTables' ability to sort multiple columns at the
- * same time (activated by shift-click by the user).
- * @type boolean
- * @default true
- *
- * @dtopt Options
- * @name DataTable.defaults.orderMulti
- *
- * @example
- * // Disable multiple column sorting ability
- * $(document).ready( function () {
- * $('#example').dataTable( {
- * "orderMulti": false
- * } );
- * } );
+ * Function to set data for a cell in the column. You should <b>never</b>
+ * set the data directly to _aData internally in DataTables - always use
+ * this method. It allows mData to function as required. This function
+ * is automatically assigned by the column initialisation method
*/
- "bSortMulti": true,
-
+ "fnSetData": null,
/**
- * Allows control over whether DataTables should use the top (true) unique
- * cell that is found for a single column, or the bottom (false - default).
- * This is useful when using complex headers.
- * @type boolean
- * @default false
- *
- * @dtopt Options
- * @name DataTable.defaults.orderCellsTop
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "orderCellsTop": true
- * } );
- * } );
+ * Property to read the value for the cells in the column from the data
+ * source array / object. If null, then the default content is used, if a
+ * function is given then the return from the function is used.
*/
- "bSortCellsTop": false,
-
+ "mData": null,
/**
- * Enable or disable the addition of the classes `sorting\_1`, `sorting\_2` and
- * `sorting\_3` to the columns which are currently being sorted on. This is
- * presented as a feature switch as it can increase processing time (while
- * classes are removed and added) so for large data sets you might want to
- * turn this off.
- * @type boolean
- * @default true
- *
- * @dtopt Features
- * @name DataTable.defaults.orderClasses
- *
- * @example
- * $(document).ready( function () {
- * $('#example').dataTable( {
- * "orderClasses": false
- * } );
- * } );
+ * Partner property to mData which is used (only when defined) to get
+ * the data - i.e. it is basically the same as mData, but without the
+ * 'set' option, and also the data fed to it is the result from mData.
+ * This is the rendering method to match the data method of mData.
*/
- "bSortClasses": true,
-
+ "mRender": null,
/**
- * Enable or disable state saving. When enabled HTML5 `localStorage` will be
- * used to save table display information such as pagination information,
- * display length, filtering and sorting. As such when the end user reloads
- * the page the display display will match what thy had previously set up.
- *
- * Due to the use of `localStorage` the default state saving is not supported
- * in IE6 or 7. If state saving is required in those browsers, use
- * `stateSaveCallback` to provide a storage solution such as cookies.
- * @type boolean
- * @default false
- *
- * @dtopt Features
- * @name DataTable.defaults.stateSave
- *
- * @example
- * $(document).ready( function () {
- * $('#example').dataTable( {
- * "stateSave": true
- * } );
- * } );
+ * The class to apply to all TD elements in the table's TBODY for the column
*/
- "bStateSave": false,
-
+ "sClass": null,
/**
- * This function is called when a TR element is created (and all TD child
- * elements have been inserted), or registered if using a DOM source, allowing
- * manipulation of the TR element (adding classes etc).
- * @type function
- * @param {node} row "TR" element for the current row
- * @param {array} data Raw data array for this row
- * @param {int} dataIndex The index of this row in the internal aoData array
- *
- * @dtopt Callbacks
- * @name DataTable.defaults.createdRow
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "createdRow": function( row, data, dataIndex ) {
- * // Bold the grade for all 'A' grade browsers
- * if ( data[4] == "A" )
- * {
- * $('td:eq(4)', row).html( '<b>A</b>' );
- * }
- * }
- * } );
- * } );
+ * When DataTables calculates the column widths to assign to each column,
+ * it finds the longest string in each column and then constructs a
+ * temporary table and reads the widths from that. The problem with this
+ * is that "mmm" is much wider then "iiii", but the latter is a longer
+ * string - thus the calculation can go wrong (doing it properly and putting
+ * it into an DOM object and measuring that is horribly(!) slow). Thus as
+ * a "work around" we provide this option. It will append its value to the
+ * text that is found to be the longest string for the column - i.e. padding.
*/
- "fnCreatedRow": null,
+ "sContentPadding": null,
+ /**
+ * Allows a default value to be given for a column's data, and will be used
+ * whenever a null data source is encountered (this can be because mData
+ * is set to null, or because the data source itself is null).
+ */
+ "sDefaultContent": null,
/**
- * This function is called on every 'draw' event, and allows you to
- * dynamically modify any aspect you want about the created DOM.
- * @type function
- * @param {object} settings DataTables settings object
- *
- * @dtopt Callbacks
- * @name DataTable.defaults.drawCallback
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "drawCallback": function( settings ) {
- * alert( 'DataTables has redrawn the table' );
- * }
- * } );
- * } );
+ * Name for the column, allowing reference to the column by name as well as
+ * by index (needs a lookup to work by name).
*/
- "fnDrawCallback": null,
+ "sName": null,
+ /**
+ * Custom sorting data type - defines which of the available plug-ins in
+ * afnSortData the custom sorting will use - if any is defined.
+ */
+ "sSortDataType": 'std',
/**
- * Identical to fnHeaderCallback() but for the table footer this function
- * allows you to modify the table footer on every 'draw' event.
- * @type function
- * @param {node} foot "TR" element for the footer
- * @param {array} data Full table data (as derived from the original HTML)
- * @param {int} start Index for the current display starting point in the
- * display array
- * @param {int} end Index for the current display ending point in the
- * display array
- * @param {array int} display Index array to translate the visual position
- * to the full data array
- *
- * @dtopt Callbacks
- * @name DataTable.defaults.footerCallback
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "footerCallback": function( tfoot, data, start, end, display ) {
- * tfoot.getElementsByTagName('th')[0].innerHTML = "Starting index is "+start;
- * }
- * } );
- * } )
+ * Class to be applied to the header element when sorting on this column
*/
- "fnFooterCallback": null,
+ "sSortingClass": null,
+ /**
+ * Title of the column - what is seen in the TH element (nTh).
+ */
+ "sTitle": null,
/**
- * When rendering large numbers in the information element for the table
- * (i.e. "Showing 1 to 10 of 57 entries") DataTables will render large numbers
- * to have a comma separator for the 'thousands' units (e.g. 1 million is
- * rendered as "1,000,000") to help readability for the end user. This
- * function will override the default method DataTables uses.
- * @type function
- * @member
- * @param {int} toFormat number to be formatted
- * @returns {string} formatted string for DataTables to show the number
- *
- * @dtopt Callbacks
- * @name DataTable.defaults.formatNumber
- *
- * @example
- * // Format a number using a single quote for the separator (note that
- * // this can also be done with the language.thousands option)
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "formatNumber": function ( toFormat ) {
- * return toFormat.toString().replace(
- * /\B(?=(\d{3})+(?!\d))/g, "'"
- * );
- * };
- * } );
- * } );
+ * Column sorting and filtering type
*/
- "fnFormatNumber": function ( toFormat ) {
- return toFormat.toString().replace(
- /\B(?=(\d{3})+(?!\d))/g,
- this.oLanguage.sThousands
- );
- },
+ "sType": null,
+ /**
+ * Width of the column
+ */
+ "sWidth": null,
/**
- * This function is called on every 'draw' event, and allows you to
- * dynamically modify the header row. This can be used to calculate and
- * display useful information about the table.
- * @type function
- * @param {node} head "TR" element for the header
- * @param {array} data Full table data (as derived from the original HTML)
- * @param {int} start Index for the current display starting point in the
- * display array
- * @param {int} end Index for the current display ending point in the
- * display array
- * @param {array int} display Index array to translate the visual position
- * to the full data array
- *
- * @dtopt Callbacks
- * @name DataTable.defaults.headerCallback
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "fheaderCallback": function( head, data, start, end, display ) {
- * head.getElementsByTagName('th')[0].innerHTML = "Displaying "+(end-start)+" records";
- * }
- * } );
- * } )
+ * Width of the column when it was first "encountered"
*/
- "fnHeaderCallback": null,
+ "sWidthOrig": null,
+ /** Cached string which is the longest in the column */
+ maxLenString: null,
/**
- * The information element can be used to convey information about the current
- * state of the table. Although the internationalisation options presented by
- * DataTables are quite capable of dealing with most customisations, there may
- * be times where you wish to customise the string further. This callback
- * allows you to do exactly that.
- * @type function
- * @param {object} oSettings DataTables settings object
- * @param {int} start Starting position in data for the draw
- * @param {int} end End position in data for the draw
- * @param {int} max Total number of rows in the table (regardless of
- * filtering)
- * @param {int} total Total number of rows in the data set, after filtering
- * @param {string} pre The string that DataTables has formatted using it's
- * own rules
- * @returns {string} The string to be displayed in the information element.
- *
- * @dtopt Callbacks
- * @name DataTable.defaults.infoCallback
- *
- * @example
- * $('#example').dataTable( {
- * "infoCallback": function( settings, start, end, max, total, pre ) {
- * return start +" to "+ end;
- * }
- * } );
+ * Store for named searches
*/
- "fnInfoCallback": null,
+ searchFixed: null
+ };
+ /*
+ * Developer note: The properties of the object below are given in Hungarian
+ * notation, that was used as the interface for DataTables prior to v1.10, however
+ * from v1.10 onwards the primary interface is camel case. In order to avoid
+ * breaking backwards compatibility utterly with this change, the Hungarian
+ * version is still, internally the primary interface, but is is not documented
+ * - hence the @name tags in each doc comment. This allows a Javascript function
+ * to create a map from Hungarian notation to camel case (going the other direction
+ * would require each property to be listed, which would add around 3K to the size
+ * of DataTables, while this method is about a 0.5K hit).
+ *
+ * Ultimately this does pave the way for Hungarian notation to be dropped
+ * completely, but that is a massive amount of work and will break current
+ * installs (therefore is on-hold until v2).
+ */
+
+ /**
+ * Initialisation options that can be given to DataTables at initialisation
+ * time.
+ * @namespace
+ */
+ DataTable.defaults = {
/**
- * Called when the table has been initialised. Normally DataTables will
- * initialise sequentially and there will be no need for this function,
- * however, this does not hold true when using external language information
- * since that is obtained using an async XHR call.
- * @type function
- * @param {object} settings DataTables settings object
- * @param {object} json The JSON object request from the server - only
- * present if client-side Ajax sourced data is used
- *
- * @dtopt Callbacks
- * @name DataTable.defaults.initComplete
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "initComplete": function(settings, json) {
- * alert( 'DataTables has finished its initialisation.' );
- * }
- * } );
- * } )
+ * An array of data to use for the table, passed in at initialisation which
+ * will be used in preference to any data which is already in the DOM. This is
+ * particularly useful for constructing tables purely in Javascript, for
+ * example with a custom Ajax call.
*/
- "fnInitComplete": null,
+ "aaData": null,
/**
- * Called at the very start of each table draw and can be used to cancel the
- * draw by returning false, any other return (including undefined) results in
- * the full draw occurring).
- * @type function
- * @param {object} settings DataTables settings object
- * @returns {boolean} False will cancel the draw, anything else (including no
- * return) will allow it to complete.
- *
- * @dtopt Callbacks
- * @name DataTable.defaults.preDrawCallback
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "preDrawCallback": function( settings ) {
- * if ( $('#test').val() == 1 ) {
- * return false;
- * }
- * }
- * } );
- * } );
+ * If ordering is enabled, then DataTables will perform a first pass sort on
+ * initialisation. You can define which column(s) the sort is performed
+ * upon, and the sorting direction, with this variable. The `sorting` array
+ * should contain an array for each column to be sorted initially containing
+ * the column's index and a direction string ('asc' or 'desc').
*/
- "fnPreDrawCallback": null,
+ "aaSorting": [[0,'asc']],
/**
- * This function allows you to 'post process' each row after it have been
- * generated for each table draw, but before it is rendered on screen. This
- * function might be used for setting the row class name etc.
- * @type function
- * @param {node} row "TR" element for the current row
- * @param {array} data Raw data array for this row
- * @param {int} displayIndex The display index for the current table draw
- * @param {int} displayIndexFull The index of the data in the full list of
- * rows (after filtering)
- *
- * @dtopt Callbacks
- * @name DataTable.defaults.rowCallback
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "rowCallback": function( row, data, displayIndex, displayIndexFull ) {
- * // Bold the grade for all 'A' grade browsers
- * if ( data[4] == "A" ) {
- * $('td:eq(4)', row).html( '<b>A</b>' );
- * }
- * }
- * } );
- * } );
+ * This parameter is basically identical to the `sorting` parameter, but
+ * cannot be overridden by user interaction with the table. What this means
+ * is that you could have a column (visible or hidden) which the sorting
+ * will always be forced on first - any sorting after that (from the user)
+ * will then be performed as required. This can be useful for grouping rows
+ * together.
*/
- "fnRowCallback": null,
+ "aaSortingFixed": [],
/**
- * __Deprecated__ The functionality provided by this parameter has now been
- * superseded by that provided through `ajax`, which should be used instead.
+ * DataTables can be instructed to load data to display in the table from a
+ * Ajax source. This option defines how that Ajax call is made and where to.
*
- * This parameter allows you to override the default function which obtains
- * the data from the server so something more suitable for your application.
- * For example you could use POST data, or pull information from a Gears or
- * AIR database.
- * @type function
- * @member
- * @param {string} source HTTP source to obtain the data from (`ajax`)
- * @param {array} data A key/value pair object containing the data to send
- * to the server
- * @param {function} callback to be called on completion of the data get
- * process that will draw the data on the page.
- * @param {object} settings DataTables settings object
+ * The `ajax` property has three different modes of operation, depending on
+ * how it is defined. These are:
*
- * @dtopt Callbacks
- * @dtopt Server-side
- * @name DataTable.defaults.serverData
+ * * `string` - Set the URL from where the data should be loaded from.
+ * * `object` - Define properties for `jQuery.ajax`.
+ * * `function` - Custom data get function
*
- * @deprecated 1.10. Please use `ajax` for this functionality now.
- */
- "fnServerData": null,
-
-
- /**
- * __Deprecated__ The functionality provided by this parameter has now been
- * superseded by that provided through `ajax`, which should be used instead.
+ * `string`
+ * --------
*
- * It is often useful to send extra data to the server when making an Ajax
- * request - for example custom filtering information, and this callback
- * function makes it trivial to send extra information to the server. The
- * passed in parameter is the data set that has been constructed by
- * DataTables, and you can add to this or modify it as you require.
- * @type function
- * @param {array} data Data array (array of objects which are name/value
- * pairs) that has been constructed by DataTables and will be sent to the
- * server. In the case of Ajax sourced data with server-side processing
- * this will be an empty array, for server-side processing there will be a
- * significant number of parameters!
- * @returns {undefined} Ensure that you modify the data array passed in,
- * as this is passed by reference.
+ * As a string, the `ajax` property simply defines the URL from which
+ * DataTables will load data.
*
- * @dtopt Callbacks
- * @dtopt Server-side
- * @name DataTable.defaults.serverParams
+ * `object`
+ * --------
*
- * @deprecated 1.10. Please use `ajax` for this functionality now.
- */
- "fnServerParams": null,
-
-
- /**
- * Load the table state. With this function you can define from where, and how, the
- * state of a table is loaded. By default DataTables will load from `localStorage`
- * but you might wish to use a server-side database or cookies.
- * @type function
- * @member
- * @param {object} settings DataTables settings object
- * @param {object} callback Callback that can be executed when done. It
- * should be passed the loaded state object.
- * @return {object} The DataTables state object to be loaded
+ * As an object, the parameters in the object are passed to
+ * [jQuery.ajax](https://api.jquery.com/jQuery.ajax/) allowing fine control
+ * of the Ajax request. DataTables has a number of default parameters which
+ * you can override using this option. Please refer to the jQuery
+ * documentation for a full description of the options available, although
+ * the following parameters provide additional options in DataTables or
+ * require special consideration:
+ *
+ * * `data` - As with jQuery, `data` can be provided as an object, but it
+ * can also be used as a function to manipulate the data DataTables sends
+ * to the server. The function takes a single parameter, an object of
+ * parameters with the values that DataTables has readied for sending. An
+ * object may be returned which will be merged into the DataTables
+ * defaults, or you can add the items to the object that was passed in and
+ * not return anything from the function. This supersedes `fnServerParams`
+ * from DataTables 1.9-.
*
- * @dtopt Callbacks
- * @name DataTable.defaults.stateLoadCallback
+ * * `dataSrc` - By default DataTables will look for the property `data` (or
+ * `aaData` for compatibility with DataTables 1.9-) when obtaining data
+ * from an Ajax source or for server-side processing - this parameter
+ * allows that property to be changed. You can use Javascript dotted
+ * object notation to get a data source for multiple levels of nesting, or
+ * it my be used as a function. As a function it takes a single parameter,
+ * the JSON returned from the server, which can be manipulated as
+ * required, with the returned value being that used by DataTables as the
+ * data source for the table.
*
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "stateSave": true,
- * "stateLoadCallback": function (settings, callback) {
- * $.ajax( {
- * "url": "/state_load",
- * "dataType": "json",
- * "success": function (json) {
- * callback( json );
- * }
- * } );
- * }
- * } );
- * } );
- */
- "fnStateLoadCallback": function ( settings ) {
- try {
- return JSON.parse(
- (settings.iStateDuration === -1 ? sessionStorage : localStorage).getItem(
- 'DataTables_'+settings.sInstance+'_'+location.pathname
- )
- );
- } catch (e) {
- return {};
- }
- },
-
-
- /**
- * Callback which allows modification of the saved state prior to loading that state.
- * This callback is called when the table is loading state from the stored data, but
- * prior to the settings object being modified by the saved state. Note that for
- * plug-in authors, you should use the `stateLoadParams` event to load parameters for
- * a plug-in.
- * @type function
- * @param {object} settings DataTables settings object
- * @param {object} data The state object that is to be loaded
+ * * `success` - Should not be overridden it is used internally in
+ * DataTables. To manipulate / transform the data returned by the server
+ * use `ajax.dataSrc`, or use `ajax` as a function (see below).
*
- * @dtopt Callbacks
- * @name DataTable.defaults.stateLoadParams
+ * `function`
+ * ----------
*
- * @example
- * // Remove a saved filter, so filtering is never loaded
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "stateSave": true,
- * "stateLoadParams": function (settings, data) {
- * data.oSearch.sSearch = "";
- * }
- * } );
- * } );
+ * As a function, making the Ajax call is left up to yourself allowing
+ * complete control of the Ajax request. Indeed, if desired, a method other
+ * than Ajax could be used to obtain the required data, such as Web storage
+ * or an AIR database.
*
- * @example
- * // Disallow state loading by returning false
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "stateSave": true,
- * "stateLoadParams": function (settings, data) {
- * return false;
- * }
- * } );
- * } );
+ * The function is given four parameters and no return is required. The
+ * parameters are:
+ *
+ * 1. _object_ - Data to send to the server
+ * 2. _function_ - Callback function that must be executed when the required
+ * data has been obtained. That data should be passed into the callback
+ * as the only parameter
+ * 3. _object_ - DataTables settings object for the table
*/
- "fnStateLoadParams": null,
+ "ajax": null,
/**
- * Callback that is called when the state has been loaded from the state saving method
- * and the DataTables settings object has been modified as a result of the loaded state.
- * @type function
- * @param {object} settings DataTables settings object
- * @param {object} data The state object that was loaded
- *
- * @dtopt Callbacks
- * @name DataTable.defaults.stateLoaded
+ * This parameter allows you to readily specify the entries in the length drop
+ * down menu that DataTables shows when pagination is enabled. It can be
+ * either a 1D array of options which will be used for both the displayed
+ * option and the value, or a 2D array which will use the array in the first
+ * position as the value, and the array in the second position as the
+ * displayed options (useful for language strings such as 'All').
*
- * @example
- * // Show an alert with the filtering value that was saved
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "stateSave": true,
- * "stateLoaded": function (settings, data) {
- * alert( 'Saved filter was: '+data.oSearch.sSearch );
- * }
- * } );
- * } );
+ * Note that the `pageLength` property will be automatically set to the
+ * first value given in this array, unless `pageLength` is also provided.
*/
- "fnStateLoaded": null,
+ "aLengthMenu": [ 10, 25, 50, 100 ],
/**
- * Save the table state. This function allows you to define where and how the state
- * information for the table is stored By default DataTables will use `localStorage`
- * but you might wish to use a server-side database or cookies.
- * @type function
- * @member
- * @param {object} settings DataTables settings object
- * @param {object} data The state object to be saved
- *
- * @dtopt Callbacks
- * @name DataTable.defaults.stateSaveCallback
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "stateSave": true,
- * "stateSaveCallback": function (settings, data) {
- * // Send an Ajax request to the server with the state object
- * $.ajax( {
- * "url": "/state_save",
- * "data": data,
- * "dataType": "json",
- * "method": "POST"
- * "success": function () {}
- * } );
- * }
- * } );
- * } );
+ * The `columns` option in the initialisation parameter allows you to define
+ * details about the way individual columns behave. For a full list of
+ * column options that can be set, please see
+ * {@link DataTable.defaults.column}. Note that if you use `columns` to
+ * define your columns, you must have an entry in the array for every single
+ * column that you have in your table (these can be null if you don't which
+ * to specify any options).
*/
- "fnStateSaveCallback": function ( settings, data ) {
- try {
- (settings.iStateDuration === -1 ? sessionStorage : localStorage).setItem(
- 'DataTables_'+settings.sInstance+'_'+location.pathname,
- JSON.stringify( data )
- );
- } catch (e) {}
- },
+ "aoColumns": null,
+
+ /**
+ * Very similar to `columns`, `columnDefs` allows you to target a specific
+ * column, multiple columns, or all columns, using the `targets` property of
+ * each object in the array. This allows great flexibility when creating
+ * tables, as the `columnDefs` arrays can be of any length, targeting the
+ * columns you specifically want. `columnDefs` may use any of the column
+ * options available: {@link DataTable.defaults.column}, but it _must_
+ * have `targets` defined in each object in the array. Values in the `targets`
+ * array may be:
+ * <ul>
+ * <li>a string - class name will be matched on the TH for the column</li>
+ * <li>0 or a positive integer - column index counting from the left</li>
+ * <li>a negative integer - column index counting from the right</li>
+ * <li>the string "_all" - all columns (i.e. assign a default)</li>
+ * </ul>
+ */
+ "aoColumnDefs": null,
/**
- * Callback which allows modification of the state to be saved. Called when the table
- * has changed state a new state save is required. This method allows modification of
- * the state saving object prior to actually doing the save, including addition or
- * other state properties or modification. Note that for plug-in authors, you should
- * use the `stateSaveParams` event to save parameters for a plug-in.
- * @type function
- * @param {object} settings DataTables settings object
- * @param {object} data The state object to be saved
- *
- * @dtopt Callbacks
- * @name DataTable.defaults.stateSaveParams
- *
- * @example
- * // Remove a saved filter, so filtering is never saved
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "stateSave": true,
- * "stateSaveParams": function (settings, data) {
- * data.oSearch.sSearch = "";
- * }
- * } );
- * } );
+ * Basically the same as `search`, this parameter defines the individual column
+ * filtering state at initialisation time. The array must be of the same size
+ * as the number of columns, and each element be an object with the parameters
+ * `search` and `escapeRegex` (the latter is optional). 'null' is also
+ * accepted and the default will be used.
*/
- "fnStateSaveParams": null,
+ "aoSearchCols": [],
/**
- * Duration for which the saved state information is considered valid. After this period
- * has elapsed the state will be returned to the default.
- * Value is given in seconds.
- * @type int
- * @default 7200 <i>(2 hours)</i>
- *
- * @dtopt Options
- * @name DataTable.defaults.stateDuration
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "stateDuration": 60*60*24; // 1 day
- * } );
- * } )
+ * Enable or disable automatic column width calculation. This can be disabled
+ * as an optimisation (it takes some time to calculate the widths) if the
+ * tables widths are passed in using `columns`.
*/
- "iStateDuration": 7200,
+ "bAutoWidth": true,
/**
- * When enabled DataTables will not make a request to the server for the first
- * page draw - rather it will use the data already on the page (no sorting etc
- * will be applied to it), thus saving on an XHR at load time. `deferLoading`
- * is used to indicate that deferred loading is required, but it is also used
- * to tell DataTables how many records there are in the full table (allowing
- * the information element and pagination to be displayed correctly). In the case
- * where a filtering is applied to the table on initial load, this can be
- * indicated by giving the parameter as an array, where the first element is
- * the number of records available after filtering and the second element is the
- * number of records without filtering (allowing the table information element
- * to be shown correctly).
- * @type int | array
- * @default null
- *
- * @dtopt Options
- * @name DataTable.defaults.deferLoading
- *
- * @example
- * // 57 records available in the table, no filtering applied
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "serverSide": true,
- * "ajax": "scripts/server_processing.php",
- * "deferLoading": 57
- * } );
- * } );
- *
- * @example
- * // 57 records after filtering, 100 without filtering (an initial filter applied)
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "serverSide": true,
- * "ajax": "scripts/server_processing.php",
- * "deferLoading": [ 57, 100 ],
- * "search": {
- * "search": "my_filter"
- * }
- * } );
- * } );
+ * Deferred rendering can provide DataTables with a huge speed boost when you
+ * are using an Ajax or JS data source for the table. This option, when set to
+ * true, will cause DataTables to defer the creation of the table elements for
+ * each row until they are needed for a draw - saving a significant amount of
+ * time.
*/
- "iDeferLoading": null,
+ "bDeferRender": true,
/**
- * Number of rows to display on a single page when using pagination. If
- * feature enabled (`lengthChange`) then the end user will be able to override
- * this to a custom setting using a pop-up menu.
- * @type int
- * @default 10
- *
- * @dtopt Options
- * @name DataTable.defaults.pageLength
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "pageLength": 50
- * } );
- * } )
+ * Replace a DataTable which matches the given selector and replace it with
+ * one which has the properties of the new initialisation object passed. If no
+ * table matches the selector, then the new DataTable will be constructed as
+ * per normal.
*/
- "iDisplayLength": 10,
+ "bDestroy": false,
/**
- * Define the starting point for data display when using DataTables with
- * pagination. Note that this parameter is the number of records, rather than
- * the page number, so if you have 10 records per page and want to start on
- * the third page, it should be "20".
- * @type int
- * @default 0
- *
- * @dtopt Options
- * @name DataTable.defaults.displayStart
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "displayStart": 20
- * } );
- * } )
+ * Enable or disable filtering of data. Filtering in DataTables is "smart" in
+ * that it allows the end user to input multiple words (space separated) and
+ * will match a row containing those words, even if not in the order that was
+ * specified (this allow matching across multiple columns). Note that if you
+ * wish to use filtering in DataTables this must remain 'true' - to remove the
+ * default filtering input box and retain filtering abilities, please use
+ * {@link DataTable.defaults.dom}.
*/
- "iDisplayStart": 0,
-
+ "bFilter": true,
/**
- * By default DataTables allows keyboard navigation of the table (sorting, paging,
- * and filtering) by adding a `tabindex` attribute to the required elements. This
- * allows you to tab through the controls and press the enter key to activate them.
- * The tabindex is default 0, meaning that the tab follows the flow of the document.
- * You can overrule this using this parameter if you wish. Use a value of -1 to
- * disable built-in keyboard navigation.
- * @type int
- * @default 0
- *
- * @dtopt Options
- * @name DataTable.defaults.tabIndex
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "tabIndex": 1
- * } );
- * } );
+ * Used only for compatiblity with DT1
+ * @deprecated
*/
- "iTabIndex": 0,
+ "bInfo": true,
+ /**
+ * Used only for compatiblity with DT1
+ * @deprecated
+ */
+ "bLengthChange": true,
/**
- * Classes that DataTables assigns to the various components and features
- * that it adds to the HTML table. This allows classes to be configured
- * during initialisation in addition to through the static
- * {@link DataTable.ext.oStdClasses} object).
- * @namespace
- * @name DataTable.defaults.classes
+ * Enable or disable pagination.
*/
- "oClasses": {},
+ "bPaginate": true,
/**
- * All strings that DataTables uses in the user interface that it creates
- * are defined in this object, allowing you to modified them individually or
- * completely replace them all as required.
- * @namespace
- * @name DataTable.defaults.language
+ * Enable or disable the display of a 'processing' indicator when the table is
+ * being processed (e.g. a sort). This is particularly useful for tables with
+ * large amounts of data where it can take a noticeable amount of time to sort
+ * the entries.
*/
- "oLanguage": {
- /**
- * Strings that are used for WAI-ARIA labels and controls only (these are not
- * actually visible on the page, but will be read by screenreaders, and thus
- * must be internationalised as well).
- * @namespace
- * @name DataTable.defaults.language.aria
- */
- "oAria": {
- /**
- * ARIA label that is added to the table headers when the column may be
- * sorted ascending by activing the column (click or return when focused).
- * Note that the column header is prefixed to this string.
- * @type string
- * @default : activate to sort column ascending
- *
- * @dtopt Language
- * @name DataTable.defaults.language.aria.sortAscending
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "language": {
- * "aria": {
- * "sortAscending": " - click/return to sort ascending"
- * }
- * }
- * } );
- * } );
- */
- "sSortAscending": ": activate to sort column ascending",
+ "bProcessing": false,
- /**
- * ARIA label that is added to the table headers when the column may be
- * sorted descending by activing the column (click or return when focused).
- * Note that the column header is prefixed to this string.
- * @type string
- * @default : activate to sort column ascending
- *
- * @dtopt Language
- * @name DataTable.defaults.language.aria.sortDescending
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "language": {
- * "aria": {
- * "sortDescending": " - click/return to sort descending"
- * }
- * }
- * } );
- * } );
- */
- "sSortDescending": ": activate to sort column descending"
- },
- /**
- * Pagination string used by DataTables for the built-in pagination
- * control types.
- * @namespace
- * @name DataTable.defaults.language.paginate
- */
- "oPaginate": {
- /**
- * Text to use when using the 'full_numbers' type of pagination for the
- * button to take the user to the first page.
- * @type string
- * @default First
- *
- * @dtopt Language
- * @name DataTable.defaults.language.paginate.first
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "language": {
- * "paginate": {
- * "first": "First page"
- * }
- * }
- * } );
- * } );
- */
- "sFirst": "First",
+ /**
+ * Retrieve the DataTables object for the given selector. Note that if the
+ * table has already been initialised, this parameter will cause DataTables
+ * to simply return the object that has already been set up - it will not take
+ * account of any changes you might have made to the initialisation object
+ * passed to DataTables (setting this parameter to true is an acknowledgement
+ * that you understand this). `destroy` can be used to reinitialise a table if
+ * you need.
+ */
+ "bRetrieve": false,
- /**
- * Text to use when using the 'full_numbers' type of pagination for the
- * button to take the user to the last page.
- * @type string
- * @default Last
- *
- * @dtopt Language
- * @name DataTable.defaults.language.paginate.last
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "language": {
- * "paginate": {
- * "last": "Last page"
- * }
- * }
- * } );
- * } );
- */
- "sLast": "Last",
+ /**
+ * When vertical (y) scrolling is enabled, DataTables will force the height of
+ * the table's viewport to the given height at all times (useful for layout).
+ * However, this can look odd when filtering data down to a small data set,
+ * and the footer is left "floating" further down. This parameter (when
+ * enabled) will cause DataTables to collapse the table's viewport down when
+ * the result set will fit within the given Y height.
+ */
+ "bScrollCollapse": false,
- /**
- * Text to use for the 'next' pagination button (to take the user to the
- * next page).
- * @type string
- * @default Next
- *
- * @dtopt Language
- * @name DataTable.defaults.language.paginate.next
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "language": {
- * "paginate": {
- * "next": "Next page"
- * }
- * }
- * } );
- * } );
- */
- "sNext": "Next",
+ /**
+ * Configure DataTables to use server-side processing. Note that the
+ * `ajax` parameter must also be given in order to give DataTables a
+ * source to obtain the required data for each draw.
+ */
+ "bServerSide": false,
- /**
- * Text to use for the 'previous' pagination button (to take the user to
- * the previous page).
- * @type string
- * @default Previous
- *
- * @dtopt Language
- * @name DataTable.defaults.language.paginate.previous
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "language": {
- * "paginate": {
- * "previous": "Previous page"
- * }
- * }
- * } );
- * } );
- */
- "sPrevious": "Previous"
- },
+ /**
+ * Enable or disable sorting of columns. Sorting of individual columns can be
+ * disabled by the `sortable` option for each column.
+ */
+ "bSort": true,
- /**
- * This string is shown in preference to `zeroRecords` when the table is
- * empty of data (regardless of filtering). Note that this is an optional
- * parameter - if it is not given, the value of `zeroRecords` will be used
- * instead (either the default or given value).
- * @type string
- * @default No data available in table
- *
- * @dtopt Language
- * @name DataTable.defaults.language.emptyTable
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "language": {
- * "emptyTable": "No data available in table"
- * }
- * } );
- * } );
- */
- "sEmptyTable": "No data available in table",
+ /**
+ * Enable or display DataTables' ability to sort multiple columns at the
+ * same time (activated by shift-click by the user).
+ */
+ "bSortMulti": true,
- /**
- * This string gives information to the end user about the information
- * that is current on display on the page. The following tokens can be
- * used in the string and will be dynamically replaced as the table
- * display updates. This tokens can be placed anywhere in the string, or
- * removed as needed by the language requires:
- *
- * * `\_START\_` - Display index of the first record on the current page
- * * `\_END\_` - Display index of the last record on the current page
- * * `\_TOTAL\_` - Number of records in the table after filtering
- * * `\_MAX\_` - Number of records in the table without filtering
- * * `\_PAGE\_` - Current page number
- * * `\_PAGES\_` - Total number of pages of data in the table
- *
- * @type string
- * @default Showing _START_ to _END_ of _TOTAL_ entries
- *
- * @dtopt Language
- * @name DataTable.defaults.language.info
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "language": {
- * "info": "Showing page _PAGE_ of _PAGES_"
- * }
- * } );
- * } );
- */
- "sInfo": "Showing _START_ to _END_ of _TOTAL_ entries",
+ /**
+ * Allows control over whether DataTables should use the top (true) unique
+ * cell that is found for a single column, or the bottom (false - default).
+ * This is useful when using complex headers.
+ */
+ "bSortCellsTop": null,
- /**
- * Display information string for when the table is empty. Typically the
- * format of this string should match `info`.
- * @type string
- * @default Showing 0 to 0 of 0 entries
- *
- * @dtopt Language
- * @name DataTable.defaults.language.infoEmpty
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "language": {
- * "infoEmpty": "No entries to show"
- * }
- * } );
- * } );
- */
- "sInfoEmpty": "Showing 0 to 0 of 0 entries",
+ /**
+ * Enable or disable the addition of the classes `sorting\_1`, `sorting\_2` and
+ * `sorting\_3` to the columns which are currently being sorted on. This is
+ * presented as a feature switch as it can increase processing time (while
+ * classes are removed and added) so for large data sets you might want to
+ * turn this off.
+ */
+ "bSortClasses": true,
- /**
- * When a user filters the information in a table, this string is appended
- * to the information (`info`) to give an idea of how strong the filtering
- * is. The variable _MAX_ is dynamically updated.
- * @type string
- * @default (filtered from _MAX_ total entries)
- *
- * @dtopt Language
- * @name DataTable.defaults.language.infoFiltered
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "language": {
- * "infoFiltered": " - filtering from _MAX_ records"
- * }
- * } );
- * } );
- */
- "sInfoFiltered": "(filtered from _MAX_ total entries)",
+ /**
+ * Enable or disable state saving. When enabled HTML5 `localStorage` will be
+ * used to save table display information such as pagination information,
+ * display length, filtering and sorting. As such when the end user reloads
+ * the page the display display will match what thy had previously set up.
+ */
+ "bStateSave": false,
- /**
- * If can be useful to append extra information to the info string at times,
- * and this variable does exactly that. This information will be appended to
- * the `info` (`infoEmpty` and `infoFiltered` in whatever combination they are
- * being used) at all times.
- * @type string
- * @default <i>Empty string</i>
- *
- * @dtopt Language
- * @name DataTable.defaults.language.infoPostFix
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "language": {
- * "infoPostFix": "All records shown are derived from real information."
- * }
- * } );
- * } );
- */
- "sInfoPostFix": "",
+
+ /**
+ * This function is called when a TR element is created (and all TD child
+ * elements have been inserted), or registered if using a DOM source, allowing
+ * manipulation of the TR element (adding classes etc).
+ */
+ "fnCreatedRow": null,
- /**
- * This decimal place operator is a little different from the other
- * language options since DataTables doesn't output floating point
- * numbers, so it won't ever use this for display of a number. Rather,
- * what this parameter does is modify the sort methods of the table so
- * that numbers which are in a format which has a character other than
- * a period (`.`) as a decimal place will be sorted numerically.
- *
- * Note that numbers with different decimal places cannot be shown in
- * the same table and still be sortable, the table must be consistent.
- * However, multiple different tables on the page can use different
- * decimal place characters.
- * @type string
- * @default
- *
- * @dtopt Language
- * @name DataTable.defaults.language.decimal
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "language": {
- * "decimal": ","
- * "thousands": "."
- * }
- * } );
- * } );
- */
- "sDecimal": "",
+ /**
+ * This function is called on every 'draw' event, and allows you to
+ * dynamically modify any aspect you want about the created DOM.
+ */
+ "fnDrawCallback": null,
- /**
- * DataTables has a build in number formatter (`formatNumber`) which is
- * used to format large numbers that are used in the table information.
- * By default a comma is used, but this can be trivially changed to any
- * character you wish with this parameter.
- * @type string
- * @default ,
- *
- * @dtopt Language
- * @name DataTable.defaults.language.thousands
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "language": {
- * "thousands": "'"
- * }
- * } );
- * } );
- */
- "sThousands": ",",
+ /**
+ * Identical to fnHeaderCallback() but for the table footer this function
+ * allows you to modify the table footer on every 'draw' event.
+ */
+ "fnFooterCallback": null,
- /**
- * Detail the action that will be taken when the drop down menu for the
- * pagination length option is changed. The '_MENU_' variable is replaced
- * with a default select list of 10, 25, 50 and 100, and can be replaced
- * with a custom select box if required.
- * @type string
- * @default Show _MENU_ entries
- *
- * @dtopt Language
- * @name DataTable.defaults.language.lengthMenu
- *
- * @example
- * // Language change only
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "language": {
- * "lengthMenu": "Display _MENU_ records"
- * }
- * } );
- * } );
- *
- * @example
- * // Language and options change
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "language": {
- * "lengthMenu": 'Display <select>'+
- * '<option value="10">10</option>'+
- * '<option value="20">20</option>'+
- * '<option value="30">30</option>'+
- * '<option value="40">40</option>'+
- * '<option value="50">50</option>'+
- * '<option value="-1">All</option>'+
- * '</select> records'
- * }
- * } );
- * } );
- */
- "sLengthMenu": "Show _MENU_ entries",
+ /**
+ * When rendering large numbers in the information element for the table
+ * (i.e. "Showing 1 to 10 of 57 entries") DataTables will render large numbers
+ * to have a comma separator for the 'thousands' units (e.g. 1 million is
+ * rendered as "1,000,000") to help readability for the end user. This
+ * function will override the default method DataTables uses.
+ */
+ "fnFormatNumber": function ( toFormat ) {
+ return toFormat.toString().replace(
+ /\B(?=(\d{3})+(?!\d))/g,
+ this.oLanguage.sThousands
+ );
+ },
- /**
- * When using Ajax sourced data and during the first draw when DataTables is
- * gathering the data, this message is shown in an empty row in the table to
- * indicate to the end user the the data is being loaded. Note that this
- * parameter is not used when loading data by server-side processing, just
- * Ajax sourced data with client-side processing.
- * @type string
- * @default Loading...
- *
- * @dtopt Language
- * @name DataTable.defaults.language.loadingRecords
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "language": {
- * "loadingRecords": "Please wait - loading..."
- * }
- * } );
- * } );
- */
- "sLoadingRecords": "Loading...",
+ /**
+ * This function is called on every 'draw' event, and allows you to
+ * dynamically modify the header row. This can be used to calculate and
+ * display useful information about the table.
+ */
+ "fnHeaderCallback": null,
- /**
- * Text which is displayed when the table is processing a user action
- * (usually a sort command or similar).
- * @type string
- *
- * @dtopt Language
- * @name DataTable.defaults.language.processing
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "language": {
- * "processing": "DataTables is currently busy"
- * }
- * } );
- * } );
- */
- "sProcessing": "",
+ /**
+ * The information element can be used to convey information about the current
+ * state of the table. Although the internationalisation options presented by
+ * DataTables are quite capable of dealing with most customisations, there may
+ * be times where you wish to customise the string further. This callback
+ * allows you to do exactly that.
+ */
+ "fnInfoCallback": null,
- /**
- * Details the actions that will be taken when the user types into the
- * filtering input text box. The variable "_INPUT_", if used in the string,
- * is replaced with the HTML text box for the filtering input allowing
- * control over where it appears in the string. If "_INPUT_" is not given
- * then the input box is appended to the string automatically.
- * @type string
- * @default Search:
- *
- * @dtopt Language
- * @name DataTable.defaults.language.search
- *
- * @example
- * // Input text box will be appended at the end automatically
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "language": {
- * "search": "Filter records:"
- * }
- * } );
- * } );
- *
- * @example
- * // Specify where the filter should appear
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "language": {
- * "search": "Apply filter _INPUT_ to table"
- * }
- * } );
- * } );
- */
- "sSearch": "Search:",
+ /**
+ * Called when the table has been initialised. Normally DataTables will
+ * initialise sequentially and there will be no need for this function,
+ * however, this does not hold true when using external language information
+ * since that is obtained using an async XHR call.
+ */
+ "fnInitComplete": null,
- /**
- * Assign a `placeholder` attribute to the search `input` element
- * @type string
- * @default
- *
- * @dtopt Language
- * @name DataTable.defaults.language.searchPlaceholder
- */
- "sSearchPlaceholder": "",
+ /**
+ * Called at the very start of each table draw and can be used to cancel the
+ * draw by returning false, any other return (including undefined) results in
+ * the full draw occurring).
+ */
+ "fnPreDrawCallback": null,
- /**
- * All of the language information can be stored in a file on the
- * server-side, which DataTables will look up if this parameter is passed.
- * It must store the URL of the language file, which is in a JSON format,
- * and the object has the same properties as the oLanguage object in the
- * initialiser object (i.e. the above parameters). Please refer to one of
- * the example language files to see how this works in action.
- * @type string
- * @default <i>Empty string - i.e. disabled</i>
- *
- * @dtopt Language
- * @name DataTable.defaults.language.url
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "language": {
- * "url": "http://www.sprymedia.co.uk/dataTables/lang.txt"
- * }
- * } );
- * } );
- */
- "sUrl": "",
+ /**
+ * This function allows you to 'post process' each row after it have been
+ * generated for each table draw, but before it is rendered on screen. This
+ * function might be used for setting the row class name etc.
+ */
+ "fnRowCallback": null,
- /**
- * Text shown inside the table records when the is no information to be
- * displayed after filtering. `emptyTable` is shown when there is simply no
- * information in the table at all (regardless of filtering).
- * @type string
- * @default No matching records found
- *
- * @dtopt Language
- * @name DataTable.defaults.language.zeroRecords
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "language": {
- * "zeroRecords": "No records to display"
- * }
- * } );
- * } );
- */
- "sZeroRecords": "No matching records found"
+ /**
+ * Load the table state. With this function you can define from where, and how, the
+ * state of a table is loaded. By default DataTables will load from `localStorage`
+ * but you might wish to use a server-side database or cookies.
+ */
+ "fnStateLoadCallback": function ( settings ) {
+ try {
+ return JSON.parse(
+ (settings.iStateDuration === -1 ? sessionStorage : localStorage).getItem(
+ 'DataTables_'+settings.sInstance+'_'+location.pathname
+ )
+ );
+ } catch (e) {
+ return {};
+ }
},
/**
- * This parameter allows you to have define the global filtering state at
- * initialisation time. As an object the `search` parameter must be
- * defined, but all other parameters are optional. When `regex` is true,
- * the search string will be treated as a regular expression, when false
- * (default) it will be treated as a straight string. When `smart`
- * DataTables will use it's smart filtering methods (to word match at
- * any point in the data), when false this will not be done.
- * @namespace
- * @extends DataTable.models.oSearch
- *
- * @dtopt Options
- * @name DataTable.defaults.search
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "search": {"search": "Initial search"}
- * } );
- * } )
+ * Callback which allows modification of the saved state prior to loading that state.
+ * This callback is called when the table is loading state from the stored data, but
+ * prior to the settings object being modified by the saved state. Note that for
+ * plug-in authors, you should use the `stateLoadParams` event to load parameters for
+ * a plug-in.
*/
- "oSearch": $.extend( {}, DataTable.models.oSearch ),
+ "fnStateLoadParams": null,
/**
- * __Deprecated__ The functionality provided by this parameter has now been
- * superseded by that provided through `ajax`, which should be used instead.
- *
- * By default DataTables will look for the property `data` (or `aaData` for
- * compatibility with DataTables 1.9-) when obtaining data from an Ajax
- * source or for server-side processing - this parameter allows that
- * property to be changed. You can use Javascript dotted object notation to
- * get a data source for multiple levels of nesting.
- * @type string
- * @default data
- *
- * @dtopt Options
- * @dtopt Server-side
- * @name DataTable.defaults.ajaxDataProp
- *
- * @deprecated 1.10. Please use `ajax` for this functionality now.
+ * Callback that is called when the state has been loaded from the state saving method
+ * and the DataTables settings object has been modified as a result of the loaded state.
*/
- "sAjaxDataProp": "data",
+ "fnStateLoaded": null,
/**
- * __Deprecated__ The functionality provided by this parameter has now been
- * superseded by that provided through `ajax`, which should be used instead.
- *
- * You can instruct DataTables to load data from an external
- * source using this parameter (use aData if you want to pass data in you
- * already have). Simply provide a url a JSON object can be obtained from.
- * @type string
- * @default null
- *
- * @dtopt Options
- * @dtopt Server-side
- * @name DataTable.defaults.ajaxSource
- *
- * @deprecated 1.10. Please use `ajax` for this functionality now.
+ * Save the table state. This function allows you to define where and how the state
+ * information for the table is stored By default DataTables will use `localStorage`
+ * but you might wish to use a server-side database or cookies.
*/
- "sAjaxSource": null,
+ "fnStateSaveCallback": function ( settings, data ) {
+ try {
+ (settings.iStateDuration === -1 ? sessionStorage : localStorage).setItem(
+ 'DataTables_'+settings.sInstance+'_'+location.pathname,
+ JSON.stringify( data )
+ );
+ } catch (e) {
+ // noop
+ }
+ },
/**
- * This initialisation variable allows you to specify exactly where in the
- * DOM you want DataTables to inject the various controls it adds to the page
- * (for example you might want the pagination controls at the top of the
- * table). DIV elements (with or without a custom class) can also be added to
- * aid styling. The follow syntax is used:
- * <ul>
- * <li>The following options are allowed:
- * <ul>
- * <li>'l' - Length changing</li>
- * <li>'f' - Filtering input</li>
- * <li>'t' - The table!</li>
- * <li>'i' - Information</li>
- * <li>'p' - Pagination</li>
- * <li>'r' - pRocessing</li>
- * </ul>
- * </li>
- * <li>The following constants are allowed:
- * <ul>
- * <li>'H' - jQueryUI theme "header" classes ('fg-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix')</li>
- * <li>'F' - jQueryUI theme "footer" classes ('fg-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix')</li>
- * </ul>
- * </li>
- * <li>The following syntax is expected:
- * <ul>
- * <li>'<' and '>' - div elements</li>
- * <li>'<"class" and '>' - div with a class</li>
- * <li>'<"#id" and '>' - div with an ID</li>
- * </ul>
- * </li>
- * <li>Examples:
- * <ul>
- * <li>'<"wrapper"flipt>'</li>
- * <li>'<lf<t>ip>'</li>
- * </ul>
- * </li>
- * </ul>
- * @type string
- * @default lfrtip <i>(when `jQueryUI` is false)</i> <b>or</b>
- * <"H"lfr>t<"F"ip> <i>(when `jQueryUI` is true)</i>
- *
- * @dtopt Options
- * @name DataTable.defaults.dom
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "dom": '<"top"i>rt<"bottom"flp><"clear">'
- * } );
- * } );
+ * Callback which allows modification of the state to be saved. Called when the table
+ * has changed state a new state save is required. This method allows modification of
+ * the state saving object prior to actually doing the save, including addition or
+ * other state properties or modification. Note that for plug-in authors, you should
+ * use the `stateSaveParams` event to save parameters for a plug-in.
*/
- "sDom": "lfrtip",
+ "fnStateSaveParams": null,
/**
- * Search delay option. This will throttle full table searches that use the
- * DataTables provided search input element (it does not effect calls to
- * `dt-api search()`, providing a delay before the search is made.
- * @type integer
- * @default 0
- *
- * @dtopt Options
- * @name DataTable.defaults.searchDelay
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "searchDelay": 200
- * } );
- * } )
+ * Duration for which the saved state information is considered valid. After this period
+ * has elapsed the state will be returned to the default.
+ * Value is given in seconds.
*/
- "searchDelay": null,
+ "iStateDuration": 7200,
/**
- * DataTables features six different built-in options for the buttons to
- * display for pagination control:
- *
- * * `numbers` - Page number buttons only
- * * `simple` - 'Previous' and 'Next' buttons only
- * * 'simple_numbers` - 'Previous' and 'Next' buttons, plus page numbers
- * * `full` - 'First', 'Previous', 'Next' and 'Last' buttons
- * * `full_numbers` - 'First', 'Previous', 'Next' and 'Last' buttons, plus page numbers
- * * `first_last_numbers` - 'First' and 'Last' buttons, plus page numbers
- *
- * Further methods can be added using {@link DataTable.ext.oPagination}.
- * @type string
- * @default simple_numbers
- *
- * @dtopt Options
- * @name DataTable.defaults.pagingType
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "pagingType": "full_numbers"
- * } );
- * } )
+ * Number of rows to display on a single page when using pagination. If
+ * feature enabled (`lengthChange`) then the end user will be able to override
+ * this to a custom setting using a pop-up menu.
*/
- "sPaginationType": "simple_numbers",
+ "iDisplayLength": 10,
/**
- * Enable horizontal scrolling. When a table is too wide to fit into a
- * certain layout, or you have a large number of columns in the table, you
- * can enable x-scrolling to show the table in a viewport, which can be
- * scrolled. This property can be `true` which will allow the table to
- * scroll horizontally when needed, or any CSS unit, or a number (in which
- * case it will be treated as a pixel measurement). Setting as simply `true`
- * is recommended.
- * @type boolean|string
- * @default <i>blank string - i.e. disabled</i>
- *
- * @dtopt Features
- * @name DataTable.defaults.scrollX
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "scrollX": true,
- * "scrollCollapse": true
- * } );
- * } );
+ * Define the starting point for data display when using DataTables with
+ * pagination. Note that this parameter is the number of records, rather than
+ * the page number, so if you have 10 records per page and want to start on
+ * the third page, it should be "20".
*/
- "sScrollX": "",
+ "iDisplayStart": 0,
/**
- * This property can be used to force a DataTable to use more width than it
- * might otherwise do when x-scrolling is enabled. For example if you have a
- * table which requires to be well spaced, this parameter is useful for
- * "over-sizing" the table, and thus forcing scrolling. This property can by
- * any CSS unit, or a number (in which case it will be treated as a pixel
- * measurement).
- * @type string
- * @default <i>blank string - i.e. disabled</i>
- *
- * @dtopt Options
- * @name DataTable.defaults.scrollXInner
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "scrollX": "100%",
- * "scrollXInner": "110%"
- * } );
- * } );
+ * By default DataTables allows keyboard navigation of the table (sorting, paging,
+ * and filtering) by adding a `tabindex` attribute to the required elements. This
+ * allows you to tab through the controls and press the enter key to activate them.
+ * The tabindex is default 0, meaning that the tab follows the flow of the document.
+ * You can overrule this using this parameter if you wish. Use a value of -1 to
+ * disable built-in keyboard navigation.
*/
- "sScrollXInner": "",
+ "iTabIndex": 0,
/**
- * Enable vertical scrolling. Vertical scrolling will constrain the DataTable
- * to the given height, and enable scrolling for any data which overflows the
- * current viewport. This can be used as an alternative to paging to display
- * a lot of data in a small area (although paging and scrolling can both be
- * enabled at the same time). This property can be any CSS unit, or a number
- * (in which case it will be treated as a pixel measurement).
- * @type string
- * @default <i>blank string - i.e. disabled</i>
- *
- * @dtopt Features
- * @name DataTable.defaults.scrollY
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "scrollY": "200px",
- * "paginate": false
- * } );
- * } );
+ * Classes that DataTables assigns to the various components and features
+ * that it adds to the HTML table. This allows classes to be configured
+ * during initialisation in addition to through the static
+ * {@link DataTable.ext.oStdClasses} object).
*/
- "sScrollY": "",
+ "oClasses": {},
/**
- * __Deprecated__ The functionality provided by this parameter has now been
- * superseded by that provided through `ajax`, which should be used instead.
- *
- * Set the HTTP method that is used to make the Ajax call for server-side
- * processing or Ajax sourced data.
- * @type string
- * @default GET
- *
- * @dtopt Options
- * @dtopt Server-side
- * @name DataTable.defaults.serverMethod
- *
- * @deprecated 1.10. Please use `ajax` for this functionality now.
+ * All strings that DataTables uses in the user interface that it creates
+ * are defined in this object, allowing you to modified them individually or
+ * completely replace them all as required.
*/
- "sServerMethod": "GET",
+ "oLanguage": {
+ /**
+ * Strings that are used for WAI-ARIA labels and controls only (these are not
+ * actually visible on the page, but will be read by screenreaders, and thus
+ * must be internationalised as well).
+ */
+ "oAria": {
+ /**
+ * ARIA label that is added to the table headers when the column may be sorted
+ */
+ "orderable": ": Activate to sort",
+ /**
+ * ARIA label that is added to the table headers when the column is currently being sorted
+ */
+ "orderableReverse": ": Activate to invert sorting",
- /**
- * DataTables makes use of renderers when displaying HTML elements for
- * a table. These renderers can be added or modified by plug-ins to
- * generate suitable mark-up for a site. For example the Bootstrap
- * integration plug-in for DataTables uses a paging button renderer to
- * display pagination buttons in the mark-up required by Bootstrap.
- *
- * For further information about the renderers available see
- * DataTable.ext.renderer
- * @type string|object
- * @default null
- *
- * @name DataTable.defaults.renderer
- *
- */
- "renderer": null,
+ /**
+ * ARIA label that is added to the table headers when the column is currently being
+ * sorted and next step is to remove sorting
+ */
+ "orderableRemove": ": Activate to remove sorting",
+ paginate: {
+ first: 'First',
+ last: 'Last',
+ next: 'Next',
+ previous: 'Previous'
+ }
+ },
- /**
- * Set the data property name that DataTables should use to get a row's id
- * to set as the `id` property in the node.
- * @type string
- * @default DT_RowId
- *
- * @name DataTable.defaults.rowId
- */
- "rowId": "DT_RowId"
- };
+ /**
+ * Pagination string used by DataTables for the built-in pagination
+ * control types.
+ */
+ "oPaginate": {
+ /**
+ * Label and character for first page button («)
+ */
+ "sFirst": "\u00AB",
- _fnHungarianMap( DataTable.defaults );
+ /**
+ * Last page button (»)
+ */
+ "sLast": "\u00BB",
+ /**
+ * Next page button (›)
+ */
+ "sNext": "\u203A",
+ /**
+ * Previous page button (‹)
+ */
+ "sPrevious": "\u2039",
+ },
- /*
- * Developer note - See note in model.defaults.js about the use of Hungarian
- * notation and camel case.
- */
+ /**
+ * Plural object for the data type the table is showing
+ */
+ entries: {
+ _: "entries",
+ 1: "entry"
+ },
- /**
- * Column options that can be given to DataTables at initialisation time.
- * @namespace
- */
- DataTable.defaults.column = {
- /**
- * Define which column(s) an order will occur on for this column. This
- * allows a column's ordering to take multiple columns into account when
- * doing a sort or use the data from a different column. For example first
- * name / last name columns make sense to do a multi-column sort over the
- * two columns.
- * @type array|int
- * @default null <i>Takes the value of the column index automatically</i>
- *
- * @name DataTable.defaults.column.orderData
- * @dtopt Columns
- *
- * @example
- * // Using `columnDefs`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columnDefs": [
- * { "orderData": [ 0, 1 ], "targets": [ 0 ] },
- * { "orderData": [ 1, 0 ], "targets": [ 1 ] },
- * { "orderData": 2, "targets": [ 2 ] }
- * ]
- * } );
- * } );
- *
- * @example
- * // Using `columns`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columns": [
- * { "orderData": [ 0, 1 ] },
- * { "orderData": [ 1, 0 ] },
- * { "orderData": 2 },
- * null,
- * null
- * ]
- * } );
- * } );
- */
- "aDataSort": null,
- "iDataSort": -1,
+ /**
+ * This string is shown in preference to `zeroRecords` when the table is
+ * empty of data (regardless of filtering). Note that this is an optional
+ * parameter - if it is not given, the value of `zeroRecords` will be used
+ * instead (either the default or given value).
+ */
+ "sEmptyTable": "No data available in table",
- /**
- * You can control the default ordering direction, and even alter the
- * behaviour of the sort handler (i.e. only allow ascending ordering etc)
- * using this parameter.
- * @type array
- * @default [ 'asc', 'desc' ]
- *
- * @name DataTable.defaults.column.orderSequence
- * @dtopt Columns
- *
- * @example
- * // Using `columnDefs`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columnDefs": [
- * { "orderSequence": [ "asc" ], "targets": [ 1 ] },
- * { "orderSequence": [ "desc", "asc", "asc" ], "targets": [ 2 ] },
- * { "orderSequence": [ "desc" ], "targets": [ 3 ] }
- * ]
- * } );
- * } );
- *
- * @example
- * // Using `columns`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columns": [
- * null,
- * { "orderSequence": [ "asc" ] },
- * { "orderSequence": [ "desc", "asc", "asc" ] },
- * { "orderSequence": [ "desc" ] },
- * null
- * ]
- * } );
- * } );
- */
- "asSorting": [ 'asc', 'desc' ],
+ /**
+ * This string gives information to the end user about the information
+ * that is current on display on the page. The following tokens can be
+ * used in the string and will be dynamically replaced as the table
+ * display updates. This tokens can be placed anywhere in the string, or
+ * removed as needed by the language requires:
+ *
+ * * `\_START\_` - Display index of the first record on the current page
+ * * `\_END\_` - Display index of the last record on the current page
+ * * `\_TOTAL\_` - Number of records in the table after filtering
+ * * `\_MAX\_` - Number of records in the table without filtering
+ * * `\_PAGE\_` - Current page number
+ * * `\_PAGES\_` - Total number of pages of data in the table
+ */
+ "sInfo": "Showing _START_ to _END_ of _TOTAL_ _ENTRIES-TOTAL_",
+
+
+ /**
+ * Display information string for when the table is empty. Typically the
+ * format of this string should match `info`.
+ */
+ "sInfoEmpty": "Showing 0 to 0 of 0 _ENTRIES-TOTAL_",
+
+
+ /**
+ * When a user filters the information in a table, this string is appended
+ * to the information (`info`) to give an idea of how strong the filtering
+ * is. The variable _MAX_ is dynamically updated.
+ */
+ "sInfoFiltered": "(filtered from _MAX_ total _ENTRIES-MAX_)",
+
+
+ /**
+ * If can be useful to append extra information to the info string at times,
+ * and this variable does exactly that. This information will be appended to
+ * the `info` (`infoEmpty` and `infoFiltered` in whatever combination they are
+ * being used) at all times.
+ */
+ "sInfoPostFix": "",
+
+
+ /**
+ * This decimal place operator is a little different from the other
+ * language options since DataTables doesn't output floating point
+ * numbers, so it won't ever use this for display of a number. Rather,
+ * what this parameter does is modify the sort methods of the table so
+ * that numbers which are in a format which has a character other than
+ * a period (`.`) as a decimal place will be sorted numerically.
+ *
+ * Note that numbers with different decimal places cannot be shown in
+ * the same table and still be sortable, the table must be consistent.
+ * However, multiple different tables on the page can use different
+ * decimal place characters.
+ */
+ "sDecimal": "",
- /**
- * Enable or disable filtering on the data in this column.
- * @type boolean
- * @default true
- *
- * @name DataTable.defaults.column.searchable
- * @dtopt Columns
- *
- * @example
- * // Using `columnDefs`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columnDefs": [
- * { "searchable": false, "targets": [ 0 ] }
- * ] } );
- * } );
- *
- * @example
- * // Using `columns`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columns": [
- * { "searchable": false },
- * null,
- * null,
- * null,
- * null
- * ] } );
- * } );
- */
- "bSearchable": true,
+ /**
+ * DataTables has a build in number formatter (`formatNumber`) which is
+ * used to format large numbers that are used in the table information.
+ * By default a comma is used, but this can be trivially changed to any
+ * character you wish with this parameter.
+ */
+ "sThousands": ",",
- /**
- * Enable or disable ordering on this column.
- * @type boolean
- * @default true
- *
- * @name DataTable.defaults.column.orderable
- * @dtopt Columns
- *
- * @example
- * // Using `columnDefs`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columnDefs": [
- * { "orderable": false, "targets": [ 0 ] }
- * ] } );
- * } );
- *
- * @example
- * // Using `columns`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columns": [
- * { "orderable": false },
- * null,
- * null,
- * null,
- * null
- * ] } );
- * } );
- */
- "bSortable": true,
+ /**
+ * Detail the action that will be taken when the drop down menu for the
+ * pagination length option is changed. The '_MENU_' variable is replaced
+ * with a default select list of 10, 25, 50 and 100, and can be replaced
+ * with a custom select box if required.
+ */
+ "sLengthMenu": "_MENU_ _ENTRIES_ per page",
- /**
- * Enable or disable the display of this column.
- * @type boolean
- * @default true
- *
- * @name DataTable.defaults.column.visible
- * @dtopt Columns
- *
- * @example
- * // Using `columnDefs`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columnDefs": [
- * { "visible": false, "targets": [ 0 ] }
- * ] } );
- * } );
- *
- * @example
- * // Using `columns`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columns": [
- * { "visible": false },
- * null,
- * null,
- * null,
- * null
- * ] } );
- * } );
- */
- "bVisible": true,
+ /**
+ * When using Ajax sourced data and during the first draw when DataTables is
+ * gathering the data, this message is shown in an empty row in the table to
+ * indicate to the end user the the data is being loaded. Note that this
+ * parameter is not used when loading data by server-side processing, just
+ * Ajax sourced data with client-side processing.
+ */
+ "sLoadingRecords": "Loading...",
- /**
- * Developer definable function that is called whenever a cell is created (Ajax source,
- * etc) or processed for input (DOM source). This can be used as a compliment to mRender
- * allowing you to modify the DOM element (add background colour for example) when the
- * element is available.
- * @type function
- * @param {element} td The TD node that has been created
- * @param {*} cellData The Data for the cell
- * @param {array|object} rowData The data for the whole row
- * @param {int} row The row index for the aoData data store
- * @param {int} col The column index for aoColumns
- *
- * @name DataTable.defaults.column.createdCell
- * @dtopt Columns
- *
- * @example
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columnDefs": [ {
- * "targets": [3],
- * "createdCell": function (td, cellData, rowData, row, col) {
- * if ( cellData == "1.7" ) {
- * $(td).css('color', 'blue')
- * }
- * }
- * } ]
- * });
- * } );
- */
- "fnCreatedCell": null,
+ /**
+ * Text which is displayed when the table is processing a user action
+ * (usually a sort command or similar).
+ */
+ "sProcessing": "",
- /**
- * This parameter has been replaced by `data` in DataTables to ensure naming
- * consistency. `dataProp` can still be used, as there is backwards
- * compatibility in DataTables for this option, but it is strongly
- * recommended that you use `data` in preference to `dataProp`.
- * @name DataTable.defaults.column.dataProp
- */
+ /**
+ * Details the actions that will be taken when the user types into the
+ * filtering input text box. The variable "_INPUT_", if used in the string,
+ * is replaced with the HTML text box for the filtering input allowing
+ * control over where it appears in the string. If "_INPUT_" is not given
+ * then the input box is appended to the string automatically.
+ */
+ "sSearch": "Search:",
- /**
- * This property can be used to read data from any data source property,
- * including deeply nested objects / properties. `data` can be given in a
- * number of different ways which effect its behaviour:
- *
- * * `integer` - treated as an array index for the data source. This is the
- * default that DataTables uses (incrementally increased for each column).
- * * `string` - read an object property from the data source. There are
- * three 'special' options that can be used in the string to alter how
- * DataTables reads the data from the source object:
- * * `.` - Dotted Javascript notation. Just as you use a `.` in
- * Javascript to read from nested objects, so to can the options
- * specified in `data`. For example: `browser.version` or
- * `browser.name`. If your object parameter name contains a period, use
- * `\\` to escape it - i.e. `first\\.name`.
- * * `[]` - Array notation. DataTables can automatically combine data
- * from and array source, joining the data with the characters provided
- * between the two brackets. For example: `name[, ]` would provide a
- * comma-space separated list from the source array. If no characters
- * are provided between the brackets, the original array source is
- * returned.
- * * `()` - Function notation. Adding `()` to the end of a parameter will
- * execute a function of the name given. For example: `browser()` for a
- * simple function on the data source, `browser.version()` for a
- * function in a nested property or even `browser().version` to get an
- * object property if the function called returns an object. Note that
- * function notation is recommended for use in `render` rather than
- * `data` as it is much simpler to use as a renderer.
- * * `null` - use the original data source for the row rather than plucking
- * data directly from it. This action has effects on two other
- * initialisation options:
- * * `defaultContent` - When null is given as the `data` option and
- * `defaultContent` is specified for the column, the value defined by
- * `defaultContent` will be used for the cell.
- * * `render` - When null is used for the `data` option and the `render`
- * option is specified for the column, the whole data source for the
- * row is used for the renderer.
- * * `function` - the function given will be executed whenever DataTables
- * needs to set or get the data for a cell in the column. The function
- * takes three parameters:
- * * Parameters:
- * * `{array|object}` The data source for the row
- * * `{string}` The type call data requested - this will be 'set' when
- * setting data or 'filter', 'display', 'type', 'sort' or undefined
- * when gathering data. Note that when `undefined` is given for the
- * type DataTables expects to get the raw data for the object back<
- * * `{*}` Data to set when the second parameter is 'set'.
- * * Return:
- * * The return value from the function is not required when 'set' is
- * the type of call, but otherwise the return is what will be used
- * for the data requested.
- *
- * Note that `data` is a getter and setter option. If you just require
- * formatting of data for output, you will likely want to use `render` which
- * is simply a getter and thus simpler to use.
- *
- * Note that prior to DataTables 1.9.2 `data` was called `mDataProp`. The
- * name change reflects the flexibility of this property and is consistent
- * with the naming of mRender. If 'mDataProp' is given, then it will still
- * be used by DataTables, as it automatically maps the old name to the new
- * if required.
- *
- * @type string|int|function|null
- * @default null <i>Use automatically calculated column index</i>
- *
- * @name DataTable.defaults.column.data
- * @dtopt Columns
- *
- * @example
- * // Read table data from objects
- * // JSON structure for each row:
- * // {
- * // "engine": {value},
- * // "browser": {value},
- * // "platform": {value},
- * // "version": {value},
- * // "grade": {value}
- * // }
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "ajaxSource": "sources/objects.txt",
- * "columns": [
- * { "data": "engine" },
- * { "data": "browser" },
- * { "data": "platform" },
- * { "data": "version" },
- * { "data": "grade" }
- * ]
- * } );
- * } );
- *
- * @example
- * // Read information from deeply nested objects
- * // JSON structure for each row:
- * // {
- * // "engine": {value},
- * // "browser": {value},
- * // "platform": {
- * // "inner": {value}
- * // },
- * // "details": [
- * // {value}, {value}
- * // ]
- * // }
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "ajaxSource": "sources/deep.txt",
- * "columns": [
- * { "data": "engine" },
- * { "data": "browser" },
- * { "data": "platform.inner" },
- * { "data": "details.0" },
- * { "data": "details.1" }
- * ]
- * } );
- * } );
- *
- * @example
- * // Using `data` as a function to provide different information for
- * // sorting, filtering and display. In this case, currency (price)
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columnDefs": [ {
- * "targets": [ 0 ],
- * "data": function ( source, type, val ) {
- * if (type === 'set') {
- * source.price = val;
- * // Store the computed display and filter values for efficiency
- * source.price_display = val=="" ? "" : "$"+numberFormat(val);
- * source.price_filter = val=="" ? "" : "$"+numberFormat(val)+" "+val;
- * return;
- * }
- * else if (type === 'display') {
- * return source.price_display;
- * }
- * else if (type === 'filter') {
- * return source.price_filter;
- * }
- * // 'sort', 'type' and undefined all just use the integer
- * return source.price;
- * }
- * } ]
- * } );
- * } );
- *
- * @example
- * // Using default content
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columnDefs": [ {
- * "targets": [ 0 ],
- * "data": null,
- * "defaultContent": "Click to edit"
- * } ]
- * } );
- * } );
- *
- * @example
- * // Using array notation - outputting a list from an array
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columnDefs": [ {
- * "targets": [ 0 ],
- * "data": "name[, ]"
- * } ]
- * } );
- * } );
- *
- */
- "mData": null,
+ /**
+ * Assign a `placeholder` attribute to the search `input` element
+ * @type string
+ * @default
+ *
+ * @dtopt Language
+ * @name DataTable.defaults.language.searchPlaceholder
+ */
+ "sSearchPlaceholder": "",
- /**
- * This property is the rendering partner to `data` and it is suggested that
- * when you want to manipulate data for display (including filtering,
- * sorting etc) without altering the underlying data for the table, use this
- * property. `render` can be considered to be the the read only companion to
- * `data` which is read / write (then as such more complex). Like `data`
- * this option can be given in a number of different ways to effect its
- * behaviour:
- *
- * * `integer` - treated as an array index for the data source. This is the
- * default that DataTables uses (incrementally increased for each column).
- * * `string` - read an object property from the data source. There are
- * three 'special' options that can be used in the string to alter how
- * DataTables reads the data from the source object:
- * * `.` - Dotted Javascript notation. Just as you use a `.` in
- * Javascript to read from nested objects, so to can the options
- * specified in `data`. For example: `browser.version` or
- * `browser.name`. If your object parameter name contains a period, use
- * `\\` to escape it - i.e. `first\\.name`.
- * * `[]` - Array notation. DataTables can automatically combine data
- * from and array source, joining the data with the characters provided
- * between the two brackets. For example: `name[, ]` would provide a
- * comma-space separated list from the source array. If no characters
- * are provided between the brackets, the original array source is
- * returned.
- * * `()` - Function notation. Adding `()` to the end of a parameter will
- * execute a function of the name given. For example: `browser()` for a
- * simple function on the data source, `browser.version()` for a
- * function in a nested property or even `browser().version` to get an
- * object property if the function called returns an object.
- * * `object` - use different data for the different data types requested by
- * DataTables ('filter', 'display', 'type' or 'sort'). The property names
- * of the object is the data type the property refers to and the value can
- * defined using an integer, string or function using the same rules as
- * `render` normally does. Note that an `_` option _must_ be specified.
- * This is the default value to use if you haven't specified a value for
- * the data type requested by DataTables.
- * * `function` - the function given will be executed whenever DataTables
- * needs to set or get the data for a cell in the column. The function
- * takes three parameters:
- * * Parameters:
- * * {array|object} The data source for the row (based on `data`)
- * * {string} The type call data requested - this will be 'filter',
- * 'display', 'type' or 'sort'.
- * * {array|object} The full data source for the row (not based on
- * `data`)
- * * Return:
- * * The return value from the function is what will be used for the
- * data requested.
- *
- * @type string|int|function|object|null
- * @default null Use the data source value.
- *
- * @name DataTable.defaults.column.render
- * @dtopt Columns
- *
- * @example
- * // Create a comma separated list from an array of objects
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "ajaxSource": "sources/deep.txt",
- * "columns": [
- * { "data": "engine" },
- * { "data": "browser" },
- * {
- * "data": "platform",
- * "render": "[, ].name"
- * }
- * ]
- * } );
- * } );
- *
- * @example
- * // Execute a function to obtain data
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columnDefs": [ {
- * "targets": [ 0 ],
- * "data": null, // Use the full data source object for the renderer's source
- * "render": "browserName()"
- * } ]
- * } );
- * } );
- *
- * @example
- * // As an object, extracting different data for the different types
- * // This would be used with a data source such as:
- * // { "phone": 5552368, "phone_filter": "5552368 555-2368", "phone_display": "555-2368" }
- * // Here the `phone` integer is used for sorting and type detection, while `phone_filter`
- * // (which has both forms) is used for filtering for if a user inputs either format, while
- * // the formatted phone number is the one that is shown in the table.
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columnDefs": [ {
- * "targets": [ 0 ],
- * "data": null, // Use the full data source object for the renderer's source
- * "render": {
- * "_": "phone",
- * "filter": "phone_filter",
- * "display": "phone_display"
- * }
- * } ]
- * } );
- * } );
- *
- * @example
- * // Use as a function to create a link from the data source
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columnDefs": [ {
- * "targets": [ 0 ],
- * "data": "download_link",
- * "render": function ( data, type, full ) {
- * return '<a href="'+data+'">Download</a>';
- * }
- * } ]
- * } );
- * } );
- */
- "mRender": null,
+ /**
+ * All of the language information can be stored in a file on the
+ * server-side, which DataTables will look up if this parameter is passed.
+ * It must store the URL of the language file, which is in a JSON format,
+ * and the object has the same properties as the oLanguage object in the
+ * initialiser object (i.e. the above parameters). Please refer to one of
+ * the example language files to see how this works in action.
+ */
+ "sUrl": "",
- /**
- * Change the cell type created for the column - either TD cells or TH cells. This
- * can be useful as TH cells have semantic meaning in the table body, allowing them
- * to act as a header for a row (you may wish to add scope='row' to the TH elements).
- * @type string
- * @default td
- *
- * @name DataTable.defaults.column.cellType
- * @dtopt Columns
- *
- * @example
- * // Make the first column use TH cells
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columnDefs": [ {
- * "targets": [ 0 ],
- * "cellType": "th"
- * } ]
- * } );
- * } );
- */
- "sCellType": "td",
+ /**
+ * Text shown inside the table records when the is no information to be
+ * displayed after filtering. `emptyTable` is shown when there is simply no
+ * information in the table at all (regardless of filtering).
+ */
+ "sZeroRecords": "No matching records found"
+ },
/**
- * Class to give to each cell in this column.
- * @type string
- * @default <i>Empty string</i>
- *
- * @name DataTable.defaults.column.class
- * @dtopt Columns
- *
- * @example
- * // Using `columnDefs`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columnDefs": [
- * { "class": "my_class", "targets": [ 0 ] }
- * ]
- * } );
- * } );
- *
- * @example
- * // Using `columns`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columns": [
- * { "class": "my_class" },
- * null,
- * null,
- * null,
- * null
- * ]
- * } );
- * } );
+ * This parameter allows you to have define the global filtering state at
+ * initialisation time. As an object the `search` parameter must be
+ * defined, but all other parameters are optional. When `regex` is true,
+ * the search string will be treated as a regular expression, when false
+ * (default) it will be treated as a straight string. When `smart`
+ * DataTables will use it's smart filtering methods (to word match at
+ * any point in the data), when false this will not be done.
*/
- "sClass": "",
+ "oSearch": $.extend( {}, DataTable.models.oSearch ),
+
/**
- * When DataTables calculates the column widths to assign to each column,
- * it finds the longest string in each column and then constructs a
- * temporary table and reads the widths from that. The problem with this
- * is that "mmm" is much wider then "iiii", but the latter is a longer
- * string - thus the calculation can go wrong (doing it properly and putting
- * it into an DOM object and measuring that is horribly(!) slow). Thus as
- * a "work around" we provide this option. It will append its value to the
- * text that is found to be the longest string for the column - i.e. padding.
- * Generally you shouldn't need this!
- * @type string
- * @default <i>Empty string<i>
- *
- * @name DataTable.defaults.column.contentPadding
- * @dtopt Columns
- *
- * @example
- * // Using `columns`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columns": [
- * null,
- * null,
- * null,
- * {
- * "contentPadding": "mmm"
- * }
- * ]
- * } );
- * } );
+ * Table and control layout. This replaces the legacy `dom` option.
*/
- "sContentPadding": "",
+ layout: {
+ topStart: 'pageLength',
+ topEnd: 'search',
+ bottomStart: 'info',
+ bottomEnd: 'paging'
+ },
/**
- * Allows a default value to be given for a column's data, and will be used
- * whenever a null data source is encountered (this can be because `data`
- * is set to null, or because the data source itself is null).
- * @type string
- * @default null
- *
- * @name DataTable.defaults.column.defaultContent
- * @dtopt Columns
- *
- * @example
- * // Using `columnDefs`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columnDefs": [
- * {
- * "data": null,
- * "defaultContent": "Edit",
- * "targets": [ -1 ]
- * }
- * ]
- * } );
- * } );
- *
- * @example
- * // Using `columns`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columns": [
- * null,
- * null,
- * null,
- * {
- * "data": null,
- * "defaultContent": "Edit"
- * }
- * ]
- * } );
- * } );
+ * Legacy DOM layout option
*/
- "sDefaultContent": null,
+ "sDom": null,
/**
- * This parameter is only used in DataTables' server-side processing. It can
- * be exceptionally useful to know what columns are being displayed on the
- * client side, and to map these to database fields. When defined, the names
- * also allow DataTables to reorder information from the server if it comes
- * back in an unexpected order (i.e. if you switch your columns around on the
- * client-side, your server-side code does not also need updating).
- * @type string
- * @default <i>Empty string</i>
- *
- * @name DataTable.defaults.column.name
- * @dtopt Columns
- *
- * @example
- * // Using `columnDefs`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columnDefs": [
- * { "name": "engine", "targets": [ 0 ] },
- * { "name": "browser", "targets": [ 1 ] },
- * { "name": "platform", "targets": [ 2 ] },
- * { "name": "version", "targets": [ 3 ] },
- * { "name": "grade", "targets": [ 4 ] }
- * ]
- * } );
- * } );
- *
- * @example
- * // Using `columns`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columns": [
- * { "name": "engine" },
- * { "name": "browser" },
- * { "name": "platform" },
- * { "name": "version" },
- * { "name": "grade" }
- * ]
- * } );
- * } );
+ * Search delay option. This will throttle full table searches that use the
+ * DataTables provided search input element (it does not effect calls to
+ * `dt-api search()`, providing a delay before the search is made.
*/
- "sName": "",
+ "searchDelay": null,
/**
- * Defines a data source type for the ordering which can be used to read
- * real-time information from the table (updating the internally cached
- * version) prior to ordering. This allows ordering to occur on user
- * editable elements such as form inputs.
- * @type string
- * @default std
- *
- * @name DataTable.defaults.column.orderDataType
- * @dtopt Columns
- *
- * @example
- * // Using `columnDefs`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columnDefs": [
- * { "orderDataType": "dom-text", "targets": [ 2, 3 ] },
- * { "type": "numeric", "targets": [ 3 ] },
- * { "orderDataType": "dom-select", "targets": [ 4 ] },
- * { "orderDataType": "dom-checkbox", "targets": [ 5 ] }
- * ]
- * } );
- * } );
+ * DataTables features six different built-in options for the buttons to
+ * display for pagination control:
*
- * @example
- * // Using `columns`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columns": [
- * null,
- * null,
- * { "orderDataType": "dom-text" },
- * { "orderDataType": "dom-text", "type": "numeric" },
- * { "orderDataType": "dom-select" },
- * { "orderDataType": "dom-checkbox" }
- * ]
- * } );
- * } );
+ * * `numbers` - Page number buttons only
+ * * `simple` - 'Previous' and 'Next' buttons only
+ * * 'simple_numbers` - 'Previous' and 'Next' buttons, plus page numbers
+ * * `full` - 'First', 'Previous', 'Next' and 'Last' buttons
+ * * `full_numbers` - 'First', 'Previous', 'Next' and 'Last' buttons, plus page numbers
+ * * `first_last_numbers` - 'First' and 'Last' buttons, plus page numbers
*/
- "sSortDataType": "std",
+ "sPaginationType": "full_numbers",
/**
- * The title of this column.
- * @type string
- * @default null <i>Derived from the 'TH' value for this column in the
- * original HTML table.</i>
- *
- * @name DataTable.defaults.column.title
- * @dtopt Columns
- *
- * @example
- * // Using `columnDefs`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columnDefs": [
- * { "title": "My column title", "targets": [ 0 ] }
- * ]
- * } );
- * } );
- *
- * @example
- * // Using `columns`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columns": [
- * { "title": "My column title" },
- * null,
- * null,
- * null,
- * null
- * ]
- * } );
- * } );
+ * Enable horizontal scrolling. When a table is too wide to fit into a
+ * certain layout, or you have a large number of columns in the table, you
+ * can enable x-scrolling to show the table in a viewport, which can be
+ * scrolled. This property can be `true` which will allow the table to
+ * scroll horizontally when needed, or any CSS unit, or a number (in which
+ * case it will be treated as a pixel measurement). Setting as simply `true`
+ * is recommended.
*/
- "sTitle": null,
+ "sScrollX": "",
/**
- * The type allows you to specify how the data for this column will be
- * ordered. Four types (string, numeric, date and html (which will strip
- * HTML tags before ordering)) are currently available. Note that only date
- * formats understood by Javascript's Date() object will be accepted as type
- * date. For example: "Mar 26, 2008 5:03 PM". May take the values: 'string',
- * 'numeric', 'date' or 'html' (by default). Further types can be adding
- * through plug-ins.
- * @type string
- * @default null <i>Auto-detected from raw data</i>
- *
- * @name DataTable.defaults.column.type
- * @dtopt Columns
- *
- * @example
- * // Using `columnDefs`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columnDefs": [
- * { "type": "html", "targets": [ 0 ] }
- * ]
- * } );
- * } );
- *
- * @example
- * // Using `columns`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columns": [
- * { "type": "html" },
- * null,
- * null,
- * null,
- * null
- * ]
- * } );
- * } );
+ * This property can be used to force a DataTable to use more width than it
+ * might otherwise do when x-scrolling is enabled. For example if you have a
+ * table which requires to be well spaced, this parameter is useful for
+ * "over-sizing" the table, and thus forcing scrolling. This property can by
+ * any CSS unit, or a number (in which case it will be treated as a pixel
+ * measurement).
*/
- "sType": null,
+ "sScrollXInner": "",
/**
- * Defining the width of the column, this parameter may take any CSS value
- * (3em, 20px etc). DataTables applies 'smart' widths to columns which have not
- * been given a specific width through this interface ensuring that the table
- * remains readable.
- * @type string
- * @default null <i>Automatic</i>
- *
- * @name DataTable.defaults.column.width
- * @dtopt Columns
- *
- * @example
- * // Using `columnDefs`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columnDefs": [
- * { "width": "20%", "targets": [ 0 ] }
- * ]
- * } );
- * } );
- *
- * @example
- * // Using `columns`
- * $(document).ready( function() {
- * $('#example').dataTable( {
- * "columns": [
- * { "width": "20%" },
- * null,
- * null,
- * null,
- * null
- * ]
- * } );
- * } );
+ * Enable vertical scrolling. Vertical scrolling will constrain the DataTable
+ * to the given height, and enable scrolling for any data which overflows the
+ * current viewport. This can be used as an alternative to paging to display
+ * a lot of data in a small area (although paging and scrolling can both be
+ * enabled at the same time). This property can be any CSS unit, or a number
+ * (in which case it will be treated as a pixel measurement).
*/
- "sWidth": null
- };
-
- _fnHungarianMap( DataTable.defaults.column );
-
+ "sScrollY": "",
- /**
- * DataTables settings object - this holds all the information needed for a
- * given table, including configuration, data and current application of the
- * table options. DataTables does not have a single instance for each DataTable
- * with the settings attached to that instance, but rather instances of the
- * DataTable "class" are created on-the-fly as needed (typically by a
- * $().dataTable() call) and the settings object is then applied to that
- * instance.
- *
- * Note that this object is related to {@link DataTable.defaults} but this
- * one is the internal data store for DataTables's cache of columns. It should
- * NOT be manipulated outside of DataTables. Any configuration should be done
- * through the initialisation options.
- * @namespace
- * @todo Really should attach the settings object to individual instances so we
- * don't need to create new instances on each $().dataTable() call (if the
- * table already exists). It would also save passing oSettings around and
- * into every single function. However, this is a very significant
- * architecture change for DataTables and will almost certainly break
- * backwards compatibility with older installations. This is something that
- * will be done in 2.0.
- */
- DataTable.models.oSettings = {
/**
- * Primary features of DataTables and their enablement state.
- * @namespace
+ * __Deprecated__ The functionality provided by this parameter has now been
+ * superseded by that provided through `ajax`, which should be used instead.
+ *
+ * Set the HTTP method that is used to make the Ajax call for server-side
+ * processing or Ajax sourced data.
*/
- "oFeatures": {
-
- /**
- * Flag to say if DataTables should automatically try to calculate the
- * optimum table and columns widths (true) or not (false).
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type boolean
- */
- "bAutoWidth": null,
-
- /**
- * Delay the creation of TR and TD elements until they are actually
- * needed by a driven page draw. This can give a significant speed
- * increase for Ajax source and Javascript source data, but makes no
- * difference at all for DOM and server-side processing tables.
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type boolean
- */
- "bDeferRender": null,
-
- /**
- * Enable filtering on the table or not. Note that if this is disabled
- * then there is no filtering at all on the table, including fnFilter.
- * To just remove the filtering input use sDom and remove the 'f' option.
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type boolean
- */
- "bFilter": null,
-
- /**
- * Table information element (the 'Showing x of y records' div) enable
- * flag.
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type boolean
- */
- "bInfo": null,
-
- /**
- * Present a user control allowing the end user to change the page size
- * when pagination is enabled.
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type boolean
- */
- "bLengthChange": null,
-
- /**
- * Pagination enabled or not. Note that if this is disabled then length
- * changing must also be disabled.
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type boolean
- */
- "bPaginate": null,
+ "sServerMethod": "GET",
- /**
- * Processing indicator enable flag whenever DataTables is enacting a
- * user request - typically an Ajax request for server-side processing.
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type boolean
- */
- "bProcessing": null,
- /**
- * Server-side processing enabled flag - when enabled DataTables will
- * get all data from the server for every draw - there is no filtering,
- * sorting or paging done on the client-side.
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type boolean
- */
- "bServerSide": null,
+ /**
+ * DataTables makes use of renderers when displaying HTML elements for
+ * a table. These renderers can be added or modified by plug-ins to
+ * generate suitable mark-up for a site. For example the Bootstrap
+ * integration plug-in for DataTables uses a paging button renderer to
+ * display pagination buttons in the mark-up required by Bootstrap.
+ *
+ * For further information about the renderers available see
+ * DataTable.ext.renderer
+ */
+ "renderer": null,
- /**
- * Sorting enablement flag.
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type boolean
- */
- "bSort": null,
- /**
- * Multi-column sorting
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type boolean
- */
- "bSortMulti": null,
+ /**
+ * Set the data property name that DataTables should use to get a row's id
+ * to set as the `id` property in the node.
+ */
+ "rowId": "DT_RowId",
- /**
- * Apply a class to the columns which are being sorted to provide a
- * visual highlight or not. This can slow things down when enabled since
- * there is a lot of DOM interaction.
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type boolean
- */
- "bSortClasses": null,
- /**
- * State saving enablement flag.
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type boolean
- */
- "bStateSave": null
- },
+ /**
+ * Caption value
+ */
+ "caption": null
+ };
+
+ _fnHungarianMap( DataTable.defaults );
+
+ /*
+ * Developer note - See note in model.defaults.js about the use of Hungarian
+ * notation and camel case.
+ */
+
+ /**
+ * Column options that can be given to DataTables at initialisation time.
+ * @namespace
+ */
+ DataTable.defaults.column = {
/**
- * Scrolling settings for a table.
- * @namespace
+ * Define which column(s) an order will occur on for this column. This
+ * allows a column's ordering to take multiple columns into account when
+ * doing a sort or use the data from a different column. For example first
+ * name / last name columns make sense to do a multi-column sort over the
+ * two columns.
*/
- "oScroll": {
- /**
- * When the table is shorter in height than sScrollY, collapse the
- * table container down to the height of the table (when true).
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type boolean
- */
- "bCollapse": null,
+ "aDataSort": null,
+ "iDataSort": -1,
- /**
- * Width of the scrollbar for the web-browser's platform. Calculated
- * during table initialisation.
- * @type int
- * @default 0
- */
- "iBarWidth": 0,
+ ariaTitle: '',
- /**
- * Viewport width for horizontal scrolling. Horizontal scrolling is
- * disabled if an empty string.
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type string
- */
- "sX": null,
- /**
- * Width to expand the table to when using x-scrolling. Typically you
- * should not need to use this.
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type string
- * @deprecated
- */
- "sXInner": null,
+ /**
+ * You can control the default ordering direction, and even alter the
+ * behaviour of the sort handler (i.e. only allow ascending ordering etc)
+ * using this parameter.
+ */
+ "asSorting": [ 'asc', 'desc', '' ],
- /**
- * Viewport height for vertical scrolling. Vertical scrolling is disabled
- * if an empty string.
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type string
- */
- "sY": null
- },
/**
- * Language information for the table.
- * @namespace
- * @extends DataTable.defaults.oLanguage
+ * Enable or disable filtering on the data in this column.
*/
- "oLanguage": {
- /**
- * Information callback function. See
- * {@link DataTable.defaults.fnInfoCallback}
- * @type function
- * @default null
- */
- "fnInfoCallback": null
- },
+ "bSearchable": true,
+
/**
- * Browser support parameters
- * @namespace
+ * Enable or disable ordering on this column.
*/
- "oBrowser": {
- /**
- * Indicate if the browser incorrectly calculates width:100% inside a
- * scrolling element (IE6/7)
- * @type boolean
- * @default false
- */
- "bScrollOversize": false,
+ "bSortable": true,
- /**
- * Determine if the vertical scrollbar is on the right or left of the
- * scrolling container - needed for rtl language layout, although not
- * all browsers move the scrollbar (Safari).
- * @type boolean
- * @default false
- */
- "bScrollbarLeft": false,
- /**
- * Flag for if `getBoundingClientRect` is fully supported or not
- * @type boolean
- * @default false
- */
- "bBounding": false,
+ /**
+ * Enable or disable the display of this column.
+ */
+ "bVisible": true,
- /**
- * Browser scrollbar width
- * @type integer
- * @default 0
- */
- "barWidth": 0
- },
+
+ /**
+ * Developer definable function that is called whenever a cell is created (Ajax source,
+ * etc) or processed for input (DOM source). This can be used as a compliment to mRender
+ * allowing you to modify the DOM element (add background colour for example) when the
+ * element is available.
+ */
+ "fnCreatedCell": null,
- "ajax": null,
+ /**
+ * This property can be used to read data from any data source property,
+ * including deeply nested objects / properties. `data` can be given in a
+ * number of different ways which effect its behaviour:
+ *
+ * * `integer` - treated as an array index for the data source. This is the
+ * default that DataTables uses (incrementally increased for each column).
+ * * `string` - read an object property from the data source. There are
+ * three 'special' options that can be used in the string to alter how
+ * DataTables reads the data from the source object:
+ * * `.` - Dotted Javascript notation. Just as you use a `.` in
+ * Javascript to read from nested objects, so to can the options
+ * specified in `data`. For example: `browser.version` or
+ * `browser.name`. If your object parameter name contains a period, use
+ * `\\` to escape it - i.e. `first\\.name`.
+ * * `[]` - Array notation. DataTables can automatically combine data
+ * from and array source, joining the data with the characters provided
+ * between the two brackets. For example: `name[, ]` would provide a
+ * comma-space separated list from the source array. If no characters
+ * are provided between the brackets, the original array source is
+ * returned.
+ * * `()` - Function notation. Adding `()` to the end of a parameter will
+ * execute a function of the name given. For example: `browser()` for a
+ * simple function on the data source, `browser.version()` for a
+ * function in a nested property or even `browser().version` to get an
+ * object property if the function called returns an object. Note that
+ * function notation is recommended for use in `render` rather than
+ * `data` as it is much simpler to use as a renderer.
+ * * `null` - use the original data source for the row rather than plucking
+ * data directly from it. This action has effects on two other
+ * initialisation options:
+ * * `defaultContent` - When null is given as the `data` option and
+ * `defaultContent` is specified for the column, the value defined by
+ * `defaultContent` will be used for the cell.
+ * * `render` - When null is used for the `data` option and the `render`
+ * option is specified for the column, the whole data source for the
+ * row is used for the renderer.
+ * * `function` - the function given will be executed whenever DataTables
+ * needs to set or get the data for a cell in the column. The function
+ * takes three parameters:
+ * * Parameters:
+ * * `{array|object}` The data source for the row
+ * * `{string}` The type call data requested - this will be 'set' when
+ * setting data or 'filter', 'display', 'type', 'sort' or undefined
+ * when gathering data. Note that when `undefined` is given for the
+ * type DataTables expects to get the raw data for the object back<
+ * * `{*}` Data to set when the second parameter is 'set'.
+ * * Return:
+ * * The return value from the function is not required when 'set' is
+ * the type of call, but otherwise the return is what will be used
+ * for the data requested.
+ *
+ * Note that `data` is a getter and setter option. If you just require
+ * formatting of data for output, you will likely want to use `render` which
+ * is simply a getter and thus simpler to use.
+ *
+ * Note that prior to DataTables 1.9.2 `data` was called `mDataProp`. The
+ * name change reflects the flexibility of this property and is consistent
+ * with the naming of mRender. If 'mDataProp' is given, then it will still
+ * be used by DataTables, as it automatically maps the old name to the new
+ * if required.
+ */
+ "mData": null,
+
+
+ /**
+ * This property is the rendering partner to `data` and it is suggested that
+ * when you want to manipulate data for display (including filtering,
+ * sorting etc) without altering the underlying data for the table, use this
+ * property. `render` can be considered to be the the read only companion to
+ * `data` which is read / write (then as such more complex). Like `data`
+ * this option can be given in a number of different ways to effect its
+ * behaviour:
+ *
+ * * `integer` - treated as an array index for the data source. This is the
+ * default that DataTables uses (incrementally increased for each column).
+ * * `string` - read an object property from the data source. There are
+ * three 'special' options that can be used in the string to alter how
+ * DataTables reads the data from the source object:
+ * * `.` - Dotted Javascript notation. Just as you use a `.` in
+ * Javascript to read from nested objects, so to can the options
+ * specified in `data`. For example: `browser.version` or
+ * `browser.name`. If your object parameter name contains a period, use
+ * `\\` to escape it - i.e. `first\\.name`.
+ * * `[]` - Array notation. DataTables can automatically combine data
+ * from and array source, joining the data with the characters provided
+ * between the two brackets. For example: `name[, ]` would provide a
+ * comma-space separated list from the source array. If no characters
+ * are provided between the brackets, the original array source is
+ * returned.
+ * * `()` - Function notation. Adding `()` to the end of a parameter will
+ * execute a function of the name given. For example: `browser()` for a
+ * simple function on the data source, `browser.version()` for a
+ * function in a nested property or even `browser().version` to get an
+ * object property if the function called returns an object.
+ * * `object` - use different data for the different data types requested by
+ * DataTables ('filter', 'display', 'type' or 'sort'). The property names
+ * of the object is the data type the property refers to and the value can
+ * defined using an integer, string or function using the same rules as
+ * `render` normally does. Note that an `_` option _must_ be specified.
+ * This is the default value to use if you haven't specified a value for
+ * the data type requested by DataTables.
+ * * `function` - the function given will be executed whenever DataTables
+ * needs to set or get the data for a cell in the column. The function
+ * takes three parameters:
+ * * Parameters:
+ * * {array|object} The data source for the row (based on `data`)
+ * * {string} The type call data requested - this will be 'filter',
+ * 'display', 'type' or 'sort'.
+ * * {array|object} The full data source for the row (not based on
+ * `data`)
+ * * Return:
+ * * The return value from the function is what will be used for the
+ * data requested.
+ */
+ "mRender": null,
/**
- * Array referencing the nodes which are used for the features. The
- * parameters of this object match what is allowed by sDom - i.e.
- * <ul>
- * <li>'l' - Length changing</li>
- * <li>'f' - Filtering input</li>
- * <li>'t' - The table!</li>
- * <li>'i' - Information</li>
- * <li>'p' - Pagination</li>
- * <li>'r' - pRocessing</li>
- * </ul>
- * @type array
- * @default []
+ * Change the cell type created for the column - either TD cells or TH cells. This
+ * can be useful as TH cells have semantic meaning in the table body, allowing them
+ * to act as a header for a row (you may wish to add scope='row' to the TH elements).
*/
- "aanFeatures": [],
+ "sCellType": "td",
- /**
- * Store data information - see {@link DataTable.models.oRow} for detailed
- * information.
- * @type array
- * @default []
- */
- "aoData": [],
/**
- * Array of indexes which are in the current display (after filtering etc)
- * @type array
- * @default []
+ * Class to give to each cell in this column.
*/
- "aiDisplay": [],
+ "sClass": "",
/**
- * Array of indexes for display - no filtering
- * @type array
- * @default []
+ * When DataTables calculates the column widths to assign to each column,
+ * it finds the longest string in each column and then constructs a
+ * temporary table and reads the widths from that. The problem with this
+ * is that "mmm" is much wider then "iiii", but the latter is a longer
+ * string - thus the calculation can go wrong (doing it properly and putting
+ * it into an DOM object and measuring that is horribly(!) slow). Thus as
+ * a "work around" we provide this option. It will append its value to the
+ * text that is found to be the longest string for the column - i.e. padding.
+ * Generally you shouldn't need this!
*/
- "aiDisplayMaster": [],
+ "sContentPadding": "",
- /**
- * Map of row ids to data indexes
- * @type object
- * @default {}
- */
- "aIds": {},
/**
- * Store information about each column that is in use
- * @type array
- * @default []
+ * Allows a default value to be given for a column's data, and will be used
+ * whenever a null data source is encountered (this can be because `data`
+ * is set to null, or because the data source itself is null).
*/
- "aoColumns": [],
+ "sDefaultContent": null,
- /**
- * Store information about the table's header
- * @type array
- * @default []
- */
- "aoHeader": [],
/**
- * Store information about the table's footer
- * @type array
- * @default []
+ * This parameter is only used in DataTables' server-side processing. It can
+ * be exceptionally useful to know what columns are being displayed on the
+ * client side, and to map these to database fields. When defined, the names
+ * also allow DataTables to reorder information from the server if it comes
+ * back in an unexpected order (i.e. if you switch your columns around on the
+ * client-side, your server-side code does not also need updating).
*/
- "aoFooter": [],
+ "sName": "",
- /**
- * Store the applied global search information in case we want to force a
- * research or compare the old search to a new one.
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @namespace
- * @extends DataTable.models.oSearch
- */
- "oPreviousSearch": {},
/**
- * Store the applied search for each column - see
- * {@link DataTable.models.oSearch} for the format that is used for the
- * filtering information for each column.
- * @type array
- * @default []
+ * Defines a data source type for the ordering which can be used to read
+ * real-time information from the table (updating the internally cached
+ * version) prior to ordering. This allows ordering to occur on user
+ * editable elements such as form inputs.
*/
- "aoPreSearchCols": [],
+ "sSortDataType": "std",
- /**
- * Sorting that is applied to the table. Note that the inner arrays are
- * used in the following manner:
- * <ul>
- * <li>Index 0 - column number</li>
- * <li>Index 1 - current sorting direction</li>
- * </ul>
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type array
- * @todo These inner arrays should really be objects
- */
- "aaSorting": null,
/**
- * Sorting that is always applied to the table (i.e. prefixed in front of
- * aaSorting).
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type array
- * @default []
+ * The title of this column.
*/
- "aaSortingFixed": [],
+ "sTitle": null,
- /**
- * Classes to use for the striping of a table.
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type array
- * @default []
- */
- "asStripeClasses": null,
/**
- * If restoring a table - we should restore its striping classes as well
- * @type array
- * @default []
+ * The type allows you to specify how the data for this column will be
+ * ordered. Four types (string, numeric, date and html (which will strip
+ * HTML tags before ordering)) are currently available. Note that only date
+ * formats understood by Javascript's Date() object will be accepted as type
+ * date. For example: "Mar 26, 2008 5:03 PM". May take the values: 'string',
+ * 'numeric', 'date' or 'html' (by default). Further types can be adding
+ * through plug-ins.
*/
- "asDestroyStripes": [],
+ "sType": null,
- /**
- * If restoring a table - we should restore its width
- * @type int
- * @default 0
- */
- "sDestroyWidth": 0,
/**
- * Callback functions array for every time a row is inserted (i.e. on a draw).
- * @type array
- * @default []
+ * Defining the width of the column, this parameter may take any CSS value
+ * (3em, 20px etc). DataTables applies 'smart' widths to columns which have not
+ * been given a specific width through this interface ensuring that the table
+ * remains readable.
*/
- "aoRowCallback": [],
+ "sWidth": null
+ };
- /**
- * Callback functions for the header on each draw.
- * @type array
- * @default []
- */
- "aoHeaderCallback": [],
+ _fnHungarianMap( DataTable.defaults.column );
- /**
- * Callback function for the footer on each draw.
- * @type array
- * @default []
- */
- "aoFooterCallback": [],
- /**
- * Array of callback functions for draw callback functions
- * @type array
- * @default []
- */
- "aoDrawCallback": [],
+ /**
+ * DataTables settings object - this holds all the information needed for a
+ * given table, including configuration, data and current application of the
+ * table options. DataTables does not have a single instance for each DataTable
+ * with the settings attached to that instance, but rather instances of the
+ * DataTable "class" are created on-the-fly as needed (typically by a
+ * $().dataTable() call) and the settings object is then applied to that
+ * instance.
+ *
+ * Note that this object is related to {@link DataTable.defaults} but this
+ * one is the internal data store for DataTables's cache of columns. It should
+ * NOT be manipulated outside of DataTables. Any configuration should be done
+ * through the initialisation options.
+ */
+ DataTable.models.oSettings = {
/**
- * Array of callback functions for row created function
- * @type array
- * @default []
+ * Primary features of DataTables and their enablement state.
*/
- "aoRowCreatedCallback": [],
+ "oFeatures": {
- /**
- * Callback functions for just before the table is redrawn. A return of
- * false will be used to cancel the draw.
- * @type array
- * @default []
- */
- "aoPreDrawCallback": [],
+ /**
+ * Flag to say if DataTables should automatically try to calculate the
+ * optimum table and columns widths (true) or not (false).
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ */
+ "bAutoWidth": null,
- /**
- * Callback functions for when the table has been initialised.
- * @type array
- * @default []
- */
- "aoInitComplete": [],
+ /**
+ * Delay the creation of TR and TD elements until they are actually
+ * needed by a driven page draw. This can give a significant speed
+ * increase for Ajax source and Javascript source data, but makes no
+ * difference at all for DOM and server-side processing tables.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ */
+ "bDeferRender": null,
+
+ /**
+ * Enable filtering on the table or not. Note that if this is disabled
+ * then there is no filtering at all on the table, including fnFilter.
+ * To just remove the filtering input use sDom and remove the 'f' option.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ */
+ "bFilter": null,
+
+ /**
+ * Used only for compatiblity with DT1
+ * @deprecated
+ */
+ "bInfo": true,
+
+ /**
+ * Used only for compatiblity with DT1
+ * @deprecated
+ */
+ "bLengthChange": true,
+
+ /**
+ * Pagination enabled or not. Note that if this is disabled then length
+ * changing must also be disabled.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ */
+ "bPaginate": null,
+
+ /**
+ * Processing indicator enable flag whenever DataTables is enacting a
+ * user request - typically an Ajax request for server-side processing.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ */
+ "bProcessing": null,
+ /**
+ * Server-side processing enabled flag - when enabled DataTables will
+ * get all data from the server for every draw - there is no filtering,
+ * sorting or paging done on the client-side.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ */
+ "bServerSide": null,
- /**
- * Callbacks for modifying the settings to be stored for state saving, prior to
- * saving state.
- * @type array
- * @default []
- */
- "aoStateSaveParams": [],
+ /**
+ * Sorting enablement flag.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ */
+ "bSort": null,
- /**
- * Callbacks for modifying the settings that have been stored for state saving
- * prior to using the stored values to restore the state.
- * @type array
- * @default []
- */
- "aoStateLoadParams": [],
+ /**
+ * Multi-column sorting
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ */
+ "bSortMulti": null,
- /**
- * Callbacks for operating on the settings object once the saved state has been
- * loaded
- * @type array
- * @default []
- */
- "aoStateLoaded": [],
+ /**
+ * Apply a class to the columns which are being sorted to provide a
+ * visual highlight or not. This can slow things down when enabled since
+ * there is a lot of DOM interaction.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ */
+ "bSortClasses": null,
- /**
- * Cache the table ID for quick access
- * @type string
- * @default <i>Empty string</i>
- */
- "sTableId": "",
+ /**
+ * State saving enablement flag.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ */
+ "bStateSave": null
+ },
- /**
- * The TABLE node for the main table
- * @type node
- * @default null
- */
- "nTable": null,
/**
- * Permanent ref to the thead element
- * @type node
- * @default null
+ * Scrolling settings for a table.
*/
- "nTHead": null,
+ "oScroll": {
+ /**
+ * When the table is shorter in height than sScrollY, collapse the
+ * table container down to the height of the table (when true).
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ */
+ "bCollapse": null,
- /**
- * Permanent ref to the tfoot element - if it exists
- * @type node
- * @default null
- */
- "nTFoot": null,
+ /**
+ * Width of the scrollbar for the web-browser's platform. Calculated
+ * during table initialisation.
+ */
+ "iBarWidth": 0,
- /**
- * Permanent ref to the tbody element
- * @type node
- * @default null
- */
- "nTBody": null,
+ /**
+ * Viewport width for horizontal scrolling. Horizontal scrolling is
+ * disabled if an empty string.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ */
+ "sX": null,
- /**
- * Cache the wrapper node (contains all DataTables controlled elements)
- * @type node
- * @default null
- */
- "nTableWrapper": null,
+ /**
+ * Width to expand the table to when using x-scrolling. Typically you
+ * should not need to use this.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @deprecated
+ */
+ "sXInner": null,
- /**
- * Indicate if when using server-side processing the loading of data
- * should be deferred until the second draw.
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type boolean
- * @default false
- */
- "bDeferLoading": false,
+ /**
+ * Viewport height for vertical scrolling. Vertical scrolling is disabled
+ * if an empty string.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ */
+ "sY": null
+ },
/**
- * Indicate if all required information has been read in
- * @type boolean
- * @default false
+ * Language information for the table.
*/
- "bInitialised": false,
+ "oLanguage": {
+ /**
+ * Information callback function. See
+ * {@link DataTable.defaults.fnInfoCallback}
+ */
+ "fnInfoCallback": null
+ },
/**
- * Information about open rows. Each object in the array has the parameters
- * 'nTr' and 'nParent'
- * @type array
- * @default []
+ * Browser support parameters
*/
- "aoOpenRows": [],
+ "oBrowser": {
+ /**
+ * Determine if the vertical scrollbar is on the right or left of the
+ * scrolling container - needed for rtl language layout, although not
+ * all browsers move the scrollbar (Safari).
+ */
+ "bScrollbarLeft": false,
- /**
- * Dictate the positioning of DataTables' control elements - see
- * {@link DataTable.model.oInit.sDom}.
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type string
- * @default null
- */
- "sDom": null,
+ /**
+ * Browser scrollbar width
+ */
+ "barWidth": 0
+ },
- /**
- * Search delay (in mS)
- * @type integer
- * @default null
- */
- "searchDelay": null,
- /**
- * Which type of pagination should be used.
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type string
- * @default two_button
- */
- "sPaginationType": "two_button",
+ "ajax": null,
- /**
- * The state duration (for `stateSave`) in seconds.
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type int
- * @default 0
- */
- "iStateDuration": 0,
/**
- * Array of callback functions for state saving. Each array element is an
- * object with the following parameters:
+ * Array referencing the nodes which are used for the features. The
+ * parameters of this object match what is allowed by sDom - i.e.
* <ul>
- * <li>function:fn - function to call. Takes two parameters, oSettings
- * and the JSON string to save that has been thus far created. Returns
- * a JSON string to be inserted into a json object
- * (i.e. '"param": [ 0, 1, 2]')</li>
- * <li>string:sName - name of callback</li>
+ * <li>'l' - Length changing</li>
+ * <li>'f' - Filtering input</li>
+ * <li>'t' - The table!</li>
+ * <li>'i' - Information</li>
+ * <li>'p' - Pagination</li>
+ * <li>'r' - pRocessing</li>
* </ul>
- * @type array
- * @default []
*/
- "aoStateSave": [],
+ "aanFeatures": [],
/**
- * Array of callback functions for state loading. Each array element is an
- * object with the following parameters:
- * <ul>
- * <li>function:fn - function to call. Takes two parameters, oSettings
- * and the object stored. May return false to cancel state loading</li>
- * <li>string:sName - name of callback</li>
- * </ul>
- * @type array
- * @default []
+ * Store data information - see {@link DataTable.models.oRow} for detailed
+ * information.
*/
- "aoStateLoad": [],
+ "aoData": [],
/**
- * State that was saved. Useful for back reference
- * @type object
- * @default null
+ * Array of indexes which are in the current display (after filtering etc)
*/
- "oSavedState": null,
+ "aiDisplay": [],
/**
- * State that was loaded. Useful for back reference
- * @type object
- * @default null
+ * Array of indexes for display - no filtering
*/
- "oLoadedState": null,
+ "aiDisplayMaster": [],
/**
- * Source url for AJAX data for the table.
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type string
- * @default null
+ * Map of row ids to data indexes
*/
- "sAjaxSource": null,
+ "aIds": {},
/**
- * Property from a given object from which to read the table data from. This
- * can be an empty string (when not server-side processing), in which case
- * it is assumed an an array is given directly.
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type string
+ * Store information about each column that is in use
*/
- "sAjaxDataProp": null,
+ "aoColumns": [],
/**
- * The last jQuery XHR object that was used for server-side data gathering.
- * This can be used for working with the XHR information in one of the
- * callbacks
- * @type object
- * @default null
+ * Store information about the table's header
*/
- "jqXHR": null,
+ "aoHeader": [],
/**
- * JSON returned from the server in the last Ajax request
- * @type object
- * @default undefined
+ * Store information about the table's footer
*/
- "json": undefined,
+ "aoFooter": [],
/**
- * Data submitted as part of the last Ajax request
- * @type object
- * @default undefined
+ * Store the applied global search information in case we want to force a
+ * research or compare the old search to a new one.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
*/
- "oAjaxData": undefined,
+ "oPreviousSearch": {},
/**
- * Function to get the server-side data.
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type function
+ * Store for named searches
*/
- "fnServerData": null,
+ searchFixed: {},
/**
- * Functions which are called prior to sending an Ajax request so extra
- * parameters can easily be sent to the server
- * @type array
- * @default []
+ * Store the applied search for each column - see
+ * {@link DataTable.models.oSearch} for the format that is used for the
+ * filtering information for each column.
*/
- "aoServerParams": [],
+ "aoPreSearchCols": [],
/**
- * Send the XHR HTTP method - GET or POST (could be PUT or DELETE if
- * required).
+ * Sorting that is applied to the table. Note that the inner arrays are
+ * used in the following manner:
+ * <ul>
+ * <li>Index 0 - column number</li>
+ * <li>Index 1 - current sorting direction</li>
+ * </ul>
* Note that this parameter will be set by the initialisation routine. To
* set a default use {@link DataTable.defaults}.
- * @type string
*/
- "sServerMethod": null,
+ "aaSorting": null,
/**
- * Format numbers for display.
+ * Sorting that is always applied to the table (i.e. prefixed in front of
+ * aaSorting).
* Note that this parameter will be set by the initialisation routine. To
* set a default use {@link DataTable.defaults}.
- * @type function
*/
- "fnFormatNumber": null,
+ "aaSortingFixed": [],
/**
- * List of options that can be used for the user selectable length menu.
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type array
- * @default []
+ * If restoring a table - we should restore its width
*/
- "aLengthMenu": null,
+ "sDestroyWidth": 0,
/**
- * Counter for the draws that the table does. Also used as a tracker for
- * server-side processing
- * @type int
- * @default 0
+ * Callback functions array for every time a row is inserted (i.e. on a draw).
*/
- "iDraw": 0,
+ "aoRowCallback": [],
/**
- * Indicate if a redraw is being done - useful for Ajax
- * @type boolean
- * @default false
+ * Callback functions for the header on each draw.
*/
- "bDrawing": false,
+ "aoHeaderCallback": [],
/**
- * Draw index (iDraw) of the last error when parsing the returned data
- * @type int
- * @default -1
+ * Callback function for the footer on each draw.
*/
- "iDrawError": -1,
+ "aoFooterCallback": [],
/**
- * Paging display length
- * @type int
- * @default 10
+ * Array of callback functions for draw callback functions
*/
- "_iDisplayLength": 10,
+ "aoDrawCallback": [],
/**
- * Paging start point - aiDisplay index
- * @type int
- * @default 0
+ * Array of callback functions for row created function
*/
- "_iDisplayStart": 0,
+ "aoRowCreatedCallback": [],
/**
- * Server-side processing - number of records in the result set
- * (i.e. before filtering), Use fnRecordsTotal rather than
- * this property to get the value of the number of records, regardless of
- * the server-side processing setting.
- * @type int
- * @default 0
- * @private
+ * Callback functions for just before the table is redrawn. A return of
+ * false will be used to cancel the draw.
*/
- "_iRecordsTotal": 0,
+ "aoPreDrawCallback": [],
/**
- * Server-side processing - number of records in the current display set
- * (i.e. after filtering). Use fnRecordsDisplay rather than
- * this property to get the value of the number of records, regardless of
- * the server-side processing setting.
- * @type boolean
- * @default 0
- * @private
+ * Callback functions for when the table has been initialised.
*/
- "_iRecordsDisplay": 0,
+ "aoInitComplete": [],
- /**
- * The classes to use for the table
- * @type object
- * @default {}
- */
- "oClasses": {},
/**
- * Flag attached to the settings object so you can check in the draw
- * callback if filtering has been done in the draw. Deprecated in favour of
- * events.
- * @type boolean
- * @default false
- * @deprecated
+ * Callbacks for modifying the settings to be stored for state saving, prior to
+ * saving state.
*/
- "bFiltered": false,
+ "aoStateSaveParams": [],
/**
- * Flag attached to the settings object so you can check in the draw
- * callback if sorting has been done in the draw. Deprecated in favour of
- * events.
- * @type boolean
- * @default false
- * @deprecated
+ * Callbacks for modifying the settings that have been stored for state saving
+ * prior to using the stored values to restore the state.
*/
- "bSorted": false,
+ "aoStateLoadParams": [],
/**
- * Indicate that if multiple rows are in the header and there is more than
- * one unique cell per column, if the top one (true) or bottom one (false)
- * should be used for sorting / title by DataTables.
- * Note that this parameter will be set by the initialisation routine. To
- * set a default use {@link DataTable.defaults}.
- * @type boolean
+ * Callbacks for operating on the settings object once the saved state has been
+ * loaded
*/
- "bSortCellsTop": null,
+ "aoStateLoaded": [],
/**
- * Initialisation object that is used for the table
- * @type object
- * @default null
+ * Cache the table ID for quick access
*/
- "oInit": null,
+ "sTableId": "",
/**
- * Destroy callback functions - for plug-ins to attach themselves to the
- * destroy so they can clean up markup and events.
- * @type array
- * @default []
+ * The TABLE node for the main table
*/
- "aoDestroyCallback": [],
-
+ "nTable": null,
/**
- * Get the number of records in the current record set, before filtering
- * @type function
+ * Permanent ref to the thead element
*/
- "fnRecordsTotal": function ()
- {
- return _fnDataSource( this ) == 'ssp' ?
- this._iRecordsTotal * 1 :
- this.aiDisplayMaster.length;
- },
+ "nTHead": null,
/**
- * Get the number of records in the current record set, after filtering
- * @type function
+ * Permanent ref to the tfoot element - if it exists
*/
- "fnRecordsDisplay": function ()
- {
- return _fnDataSource( this ) == 'ssp' ?
- this._iRecordsDisplay * 1 :
- this.aiDisplay.length;
- },
+ "nTFoot": null,
/**
- * Get the display end point - aiDisplay index
- * @type function
+ * Permanent ref to the tbody element
*/
- "fnDisplayEnd": function ()
- {
- var
- len = this._iDisplayLength,
- start = this._iDisplayStart,
- calc = start + len,
- records = this.aiDisplay.length,
- features = this.oFeatures,
- paginate = features.bPaginate;
-
- if ( features.bServerSide ) {
- return paginate === false || len === -1 ?
- start + records :
- Math.min( start+len, this._iRecordsDisplay );
- }
- else {
- return ! paginate || calc>records || len===-1 ?
- records :
- calc;
- }
- },
+ "nTBody": null,
/**
- * The DataTables object for this table
- * @type object
- * @default null
+ * Cache the wrapper node (contains all DataTables controlled elements)
*/
- "oInstance": null,
+ "nTableWrapper": null,
/**
- * Unique identifier for each instance of the DataTables object. If there
- * is an ID on the table node, then it takes that value, otherwise an
- * incrementing internal counter is used.
- * @type string
- * @default null
+ * Indicate if all required information has been read in
*/
- "sInstance": null,
+ "bInitialised": false,
/**
- * tabindex attribute value that is added to DataTables control elements, allowing
- * keyboard navigation of the table and its controls.
+ * Information about open rows. Each object in the array has the parameters
+ * 'nTr' and 'nParent'
*/
- "iTabIndex": 0,
+ "aoOpenRows": [],
/**
- * DIV container for the footer scrolling table if scrolling
+ * Dictate the positioning of DataTables' control elements - see
+ * {@link DataTable.model.oInit.sDom}.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
*/
- "nScrollHead": null,
+ "sDom": null,
/**
- * DIV container for the footer scrolling table if scrolling
+ * Search delay (in mS)
*/
- "nScrollFoot": null,
+ "searchDelay": null,
/**
- * Last applied sort
- * @type array
- * @default []
+ * Which type of pagination should be used.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
*/
- "aLastSort": [],
+ "sPaginationType": "two_button",
/**
- * Stored plug-in instances
- * @type object
- * @default {}
+ * Number of paging controls on the page. Only used for backwards compatibility
*/
- "oPlugins": {},
+ pagingControls: 0,
/**
- * Function used to get a row's id from the row's data
- * @type function
- * @default null
+ * The state duration (for `stateSave`) in seconds.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
*/
- "rowIdFn": null,
+ "iStateDuration": 0,
/**
- * Data location where to store a row's id
- * @type string
- * @default null
+ * Array of callback functions for state saving. Each array element is an
+ * object with the following parameters:
+ * <ul>
+ * <li>function:fn - function to call. Takes two parameters, oSettings
+ * and the JSON string to save that has been thus far created. Returns
+ * a JSON string to be inserted into a json object
+ * (i.e. '"param": [ 0, 1, 2]')</li>
+ * <li>string:sName - name of callback</li>
+ * </ul>
*/
- "rowId": null
- };
-
- /**
- * Extension object for DataTables that is used to provide all extension
- * options.
- *
- * Note that the `DataTable.ext` object is available through
- * `jQuery.fn.dataTable.ext` where it may be accessed and manipulated. It is
- * also aliased to `jQuery.fn.dataTableExt` for historic reasons.
- * @namespace
- * @extends DataTable.models.ext
- */
-
+ "aoStateSave": [],
- /**
- * DataTables extensions
- *
- * This namespace acts as a collection area for plug-ins that can be used to
- * extend DataTables capabilities. Indeed many of the build in methods
- * use this method to provide their own capabilities (sorting methods for
- * example).
- *
- * Note that this namespace is aliased to `jQuery.fn.dataTableExt` for legacy
- * reasons
- *
- * @namespace
- */
- DataTable.ext = _ext = {
/**
- * Buttons. For use with the Buttons extension for DataTables. This is
- * defined here so other extensions can define buttons regardless of load
- * order. It is _not_ used by DataTables core.
- *
- * @type object
- * @default {}
+ * Array of callback functions for state loading. Each array element is an
+ * object with the following parameters:
+ * <ul>
+ * <li>function:fn - function to call. Takes two parameters, oSettings
+ * and the object stored. May return false to cancel state loading</li>
+ * <li>string:sName - name of callback</li>
+ * </ul>
*/
- buttons: {},
+ "aoStateLoad": [],
+ /**
+ * State that was saved. Useful for back reference
+ */
+ "oSavedState": null,
/**
- * Element class names
- *
- * @type object
- * @default {}
+ * State that was loaded. Useful for back reference
*/
- classes: {},
+ "oLoadedState": null,
+ /**
+ * Note if draw should be blocked while getting data
+ */
+ "bAjaxDataGet": true,
/**
- * DataTables build type (expanded by the download builder)
- *
- * @type string
+ * The last jQuery XHR object that was used for server-side data gathering.
+ * This can be used for working with the XHR information in one of the
+ * callbacks
*/
- build:"bs5/dt-1.13.6",
+ "jqXHR": null,
+ /**
+ * JSON returned from the server in the last Ajax request
+ */
+ "json": undefined,
/**
- * Error reporting.
- *
- * How should DataTables report an error. Can take the value 'alert',
- * 'throw', 'none' or a function.
- *
- * @type string|function
- * @default alert
+ * Data submitted as part of the last Ajax request
*/
- errMode: "alert",
+ "oAjaxData": undefined,
+ /**
+ * Send the XHR HTTP method - GET or POST (could be PUT or DELETE if
+ * required).
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ */
+ "sServerMethod": null,
/**
- * Feature plug-ins.
- *
- * This is an array of objects which describe the feature plug-ins that are
- * available to DataTables. These feature plug-ins are then available for
- * use through the `dom` initialisation option.
- *
- * Each feature plug-in is described by an object which must have the
- * following properties:
- *
- * * `fnInit` - function that is used to initialise the plug-in,
- * * `cFeature` - a character so the feature can be enabled by the `dom`
- * instillation option. This is case sensitive.
- *
- * The `fnInit` function has the following input parameters:
- *
- * 1. `{object}` DataTables settings object: see
- * {@link DataTable.models.oSettings}
- *
- * And the following return is expected:
- *
- * * {node|null} The element which contains your feature. Note that the
- * return may also be void if your plug-in does not require to inject any
- * DOM elements into DataTables control (`dom`) - for example this might
- * be useful when developing a plug-in which allows table control via
- * keyboard entry
- *
- * @type array
- *
- * @example
- * $.fn.dataTable.ext.features.push( {
- * "fnInit": function( oSettings ) {
- * return new TableTools( { "oDTSettings": oSettings } );
- * },
- * "cFeature": "T"
- * } );
+ * Format numbers for display.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
*/
- feature: [],
+ "fnFormatNumber": null,
+ /**
+ * List of options that can be used for the user selectable length menu.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ */
+ "aLengthMenu": null,
/**
- * Row searching.
- *
- * This method of searching is complimentary to the default type based
- * searching, and a lot more comprehensive as it allows you complete control
- * over the searching logic. Each element in this array is a function
- * (parameters described below) that is called for every row in the table,
- * and your logic decides if it should be included in the searching data set
- * or not.
- *
- * Searching functions have the following input parameters:
- *
- * 1. `{object}` DataTables settings object: see
- * {@link DataTable.models.oSettings}
- * 2. `{array|object}` Data for the row to be processed (same as the
- * original format that was passed in as the data source, or an array
- * from a DOM data source
- * 3. `{int}` Row index ({@link DataTable.models.oSettings.aoData}), which
- * can be useful to retrieve the `TR` element if you need DOM interaction.
- *
- * And the following return is expected:
- *
- * * {boolean} Include the row in the searched result set (true) or not
- * (false)
- *
- * Note that as with the main search ability in DataTables, technically this
- * is "filtering", since it is subtractive. However, for consistency in
- * naming we call it searching here.
- *
- * @type array
- * @default []
- *
- * @example
- * // The following example shows custom search being applied to the
- * // fourth column (i.e. the data[3] index) based on two input values
- * // from the end-user, matching the data in a certain range.
- * $.fn.dataTable.ext.search.push(
- * function( settings, data, dataIndex ) {
- * var min = document.getElementById('min').value * 1;
- * var max = document.getElementById('max').value * 1;
- * var version = data[3] == "-" ? 0 : data[3]*1;
- *
- * if ( min == "" && max == "" ) {
- * return true;
- * }
- * else if ( min == "" && version < max ) {
- * return true;
- * }
- * else if ( min < version && "" == max ) {
- * return true;
- * }
- * else if ( min < version && version < max ) {
- * return true;
- * }
- * return false;
- * }
- * );
+ * Counter for the draws that the table does. Also used as a tracker for
+ * server-side processing
*/
- search: [],
+ "iDraw": 0,
+ /**
+ * Indicate if a redraw is being done - useful for Ajax
+ */
+ "bDrawing": false,
/**
- * Selector extensions
- *
- * The `selector` option can be used to extend the options available for the
- * selector modifier options (`selector-modifier` object data type) that
- * each of the three built in selector types offer (row, column and cell +
- * their plural counterparts). For example the Select extension uses this
- * mechanism to provide an option to select only rows, columns and cells
- * that have been marked as selected by the end user (`{selected: true}`),
- * which can be used in conjunction with the existing built in selector
- * options.
- *
- * Each property is an array to which functions can be pushed. The functions
- * take three attributes:
- *
- * * Settings object for the host table
- * * Options object (`selector-modifier` object type)
- * * Array of selected item indexes
- *
- * The return is an array of the resulting item indexes after the custom
- * selector has been applied.
- *
- * @type object
+ * Draw index (iDraw) of the last error when parsing the returned data
*/
- selector: {
- cell: [],
- column: [],
- row: []
- },
+ "iDrawError": -1,
+ /**
+ * Paging display length
+ */
+ "_iDisplayLength": 10,
/**
- * Internal functions, exposed for used in plug-ins.
- *
- * Please note that you should not need to use the internal methods for
- * anything other than a plug-in (and even then, try to avoid if possible).
- * The internal function may change between releases.
- *
- * @type object
- * @default {}
+ * Paging start point - aiDisplay index
*/
- internal: {},
+ "_iDisplayStart": 0,
+ /**
+ * Server-side processing - number of records in the result set
+ * (i.e. before filtering), Use fnRecordsTotal rather than
+ * this property to get the value of the number of records, regardless of
+ * the server-side processing setting.
+ */
+ "_iRecordsTotal": 0,
/**
- * Legacy configuration options. Enable and disable legacy options that
- * are available in DataTables.
- *
- * @type object
+ * Server-side processing - number of records in the current display set
+ * (i.e. after filtering). Use fnRecordsDisplay rather than
+ * this property to get the value of the number of records, regardless of
+ * the server-side processing setting.
*/
- legacy: {
- /**
- * Enable / disable DataTables 1.9 compatible server-side processing
- * requests
- *
- * @type boolean
- * @default null
- */
- ajax: null
- },
+ "_iRecordsDisplay": 0,
+ /**
+ * The classes to use for the table
+ */
+ "oClasses": {},
/**
- * Pagination plug-in methods.
- *
- * Each entry in this object is a function and defines which buttons should
- * be shown by the pagination rendering method that is used for the table:
- * {@link DataTable.ext.renderer.pageButton}. The renderer addresses how the
- * buttons are displayed in the document, while the functions here tell it
- * what buttons to display. This is done by returning an array of button
- * descriptions (what each button will do).
- *
- * Pagination types (the four built in options and any additional plug-in
- * options defined here) can be used through the `paginationType`
- * initialisation parameter.
- *
- * The functions defined take two parameters:
- *
- * 1. `{int} page` The current page index
- * 2. `{int} pages` The number of pages in the table
- *
- * Each function is expected to return an array where each element of the
- * array can be one of:
- *
- * * `first` - Jump to first page when activated
- * * `last` - Jump to last page when activated
- * * `previous` - Show previous page when activated
- * * `next` - Show next page when activated
- * * `{int}` - Show page of the index given
- * * `{array}` - A nested array containing the above elements to add a
- * containing 'DIV' element (might be useful for styling).
- *
- * Note that DataTables v1.9- used this object slightly differently whereby
- * an object with two functions would be defined for each plug-in. That
- * ability is still supported by DataTables 1.10+ to provide backwards
- * compatibility, but this option of use is now decremented and no longer
- * documented in DataTables 1.10+.
- *
- * @type object
- * @default {}
- *
- * @example
- * // Show previous, next and current page buttons only
- * $.fn.dataTableExt.oPagination.current = function ( page, pages ) {
- * return [ 'previous', page, 'next' ];
- * };
+ * Flag attached to the settings object so you can check in the draw
+ * callback if filtering has been done in the draw. Deprecated in favour of
+ * events.
+ * @deprecated
+ */
+ "bFiltered": false,
+
+ /**
+ * Flag attached to the settings object so you can check in the draw
+ * callback if sorting has been done in the draw. Deprecated in favour of
+ * events.
+ * @deprecated
*/
- pager: {},
-
+ "bSorted": false,
- renderer: {
- pageButton: {},
- header: {}
- },
+ /**
+ * Indicate that if multiple rows are in the header and there is more than
+ * one unique cell per column, if the top one (true) or bottom one (false)
+ * should be used for sorting / title by DataTables.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ */
+ "bSortCellsTop": null,
+ /**
+ * Initialisation object that is used for the table
+ */
+ "oInit": null,
/**
- * Ordering plug-ins - custom data source
- *
- * The extension options for ordering of data available here is complimentary
- * to the default type based ordering that DataTables typically uses. It
- * allows much greater control over the the data that is being used to
- * order a column, but is necessarily therefore more complex.
- *
- * This type of ordering is useful if you want to do ordering based on data
- * live from the DOM (for example the contents of an 'input' element) rather
- * than just the static string that DataTables knows of.
- *
- * The way these plug-ins work is that you create an array of the values you
- * wish to be ordering for the column in question and then return that
- * array. The data in the array much be in the index order of the rows in
- * the table (not the currently ordering order!). Which order data gathering
- * function is run here depends on the `dt-init columns.orderDataType`
- * parameter that is used for the column (if any).
- *
- * The functions defined take two parameters:
- *
- * 1. `{object}` DataTables settings object: see
- * {@link DataTable.models.oSettings}
- * 2. `{int}` Target column index
- *
- * Each function is expected to return an array:
- *
- * * `{array}` Data for the column to be ordering upon
- *
- * @type array
- *
- * @example
- * // Ordering using `input` node values
- * $.fn.dataTable.ext.order['dom-text'] = function ( settings, col )
- * {
- * return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) {
- * return $('input', td).val();
- * } );
- * }
+ * Destroy callback functions - for plug-ins to attach themselves to the
+ * destroy so they can clean up markup and events.
*/
- order: {},
+ "aoDestroyCallback": [],
/**
- * Type based plug-ins.
- *
- * Each column in DataTables has a type assigned to it, either by automatic
- * detection or by direct assignment using the `type` option for the column.
- * The type of a column will effect how it is ordering and search (plug-ins
- * can also make use of the column type if required).
- *
- * @namespace
+ * Get the number of records in the current record set, before filtering
*/
- type: {
- /**
- * Type detection functions.
- *
- * The functions defined in this object are used to automatically detect
- * a column's type, making initialisation of DataTables super easy, even
- * when complex data is in the table.
- *
- * The functions defined take two parameters:
- *
- * 1. `{*}` Data from the column cell to be analysed
- * 2. `{settings}` DataTables settings object. This can be used to
- * perform context specific type detection - for example detection
- * based on language settings such as using a comma for a decimal
- * place. Generally speaking the options from the settings will not
- * be required
- *
- * Each function is expected to return:
- *
- * * `{string|null}` Data type detected, or null if unknown (and thus
- * pass it on to the other type detection functions.
- *
- * @type array
- *
- * @example
- * // Currency type detection plug-in:
- * $.fn.dataTable.ext.type.detect.push(
- * function ( data, settings ) {
- * // Check the numeric part
- * if ( ! data.substring(1).match(/[0-9]/) ) {
- * return null;
- * }
- *
- * // Check prefixed by currency
- * if ( data.charAt(0) == '$' || data.charAt(0) == '£' ) {
- * return 'currency';
- * }
- * return null;
- * }
- * );
- */
- detect: [],
-
+ "fnRecordsTotal": function ()
+ {
+ return _fnDataSource( this ) == 'ssp' ?
+ this._iRecordsTotal * 1 :
+ this.aiDisplayMaster.length;
+ },
- /**
- * Type based search formatting.
- *
- * The type based searching functions can be used to pre-format the
- * data to be search on. For example, it can be used to strip HTML
- * tags or to de-format telephone numbers for numeric only searching.
- *
- * Note that is a search is not defined for a column of a given type,
- * no search formatting will be performed.
- *
- * Pre-processing of searching data plug-ins - When you assign the sType
- * for a column (or have it automatically detected for you by DataTables
- * or a type detection plug-in), you will typically be using this for
- * custom sorting, but it can also be used to provide custom searching
- * by allowing you to pre-processing the data and returning the data in
- * the format that should be searched upon. This is done by adding
- * functions this object with a parameter name which matches the sType
- * for that target column. This is the corollary of <i>afnSortData</i>
- * for searching data.
- *
- * The functions defined take a single parameter:
- *
- * 1. `{*}` Data from the column cell to be prepared for searching
- *
- * Each function is expected to return:
- *
- * * `{string|null}` Formatted string that will be used for the searching.
- *
- * @type object
- * @default {}
- *
- * @example
- * $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) {
- * return d.replace(/\n/g," ").replace( /<.*?>/g, "" );
- * }
- */
- search: {},
+ /**
+ * Get the number of records in the current record set, after filtering
+ */
+ "fnRecordsDisplay": function ()
+ {
+ return _fnDataSource( this ) == 'ssp' ?
+ this._iRecordsDisplay * 1 :
+ this.aiDisplay.length;
+ },
+ /**
+ * Get the display end point - aiDisplay index
+ */
+ "fnDisplayEnd": function ()
+ {
+ var
+ len = this._iDisplayLength,
+ start = this._iDisplayStart,
+ calc = start + len,
+ records = this.aiDisplay.length,
+ features = this.oFeatures,
+ paginate = features.bPaginate;
- /**
- * Type based ordering.
- *
- * The column type tells DataTables what ordering to apply to the table
- * when a column is sorted upon. The order for each type that is defined,
- * is defined by the functions available in this object.
- *
- * Each ordering option can be described by three properties added to
- * this object:
- *
- * * `{type}-pre` - Pre-formatting function
- * * `{type}-asc` - Ascending order function
- * * `{type}-desc` - Descending order function
- *
- * All three can be used together, only `{type}-pre` or only
- * `{type}-asc` and `{type}-desc` together. It is generally recommended
- * that only `{type}-pre` is used, as this provides the optimal
- * implementation in terms of speed, although the others are provided
- * for compatibility with existing Javascript sort functions.
- *
- * `{type}-pre`: Functions defined take a single parameter:
- *
- * 1. `{*}` Data from the column cell to be prepared for ordering
- *
- * And return:
- *
- * * `{*}` Data to be sorted upon
- *
- * `{type}-asc` and `{type}-desc`: Functions are typical Javascript sort
- * functions, taking two parameters:
- *
- * 1. `{*}` Data to compare to the second parameter
- * 2. `{*}` Data to compare to the first parameter
- *
- * And returning:
- *
- * * `{*}` Ordering match: <0 if first parameter should be sorted lower
- * than the second parameter, ===0 if the two parameters are equal and
- * >0 if the first parameter should be sorted height than the second
- * parameter.
- *
- * @type object
- * @default {}
- *
- * @example
- * // Numeric ordering of formatted numbers with a pre-formatter
- * $.extend( $.fn.dataTable.ext.type.order, {
- * "string-pre": function(x) {
- * a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" );
- * return parseFloat( a );
- * }
- * } );
- *
- * @example
- * // Case-sensitive string ordering, with no pre-formatting method
- * $.extend( $.fn.dataTable.ext.order, {
- * "string-case-asc": function(x,y) {
- * return ((x < y) ? -1 : ((x > y) ? 1 : 0));
- * },
- * "string-case-desc": function(x,y) {
- * return ((x < y) ? 1 : ((x > y) ? -1 : 0));
- * }
- * } );
- */
- order: {}
+ if ( features.bServerSide ) {
+ return paginate === false || len === -1 ?
+ start + records :
+ Math.min( start+len, this._iRecordsDisplay );
+ }
+ else {
+ return ! paginate || calc>records || len===-1 ?
+ records :
+ calc;
+ }
},
/**
- * Unique DataTables instance counter
- *
- * @type int
- * @private
+ * The DataTables object for this table
+ */
+ "oInstance": null,
+
+ /**
+ * Unique identifier for each instance of the DataTables object. If there
+ * is an ID on the table node, then it takes that value, otherwise an
+ * incrementing internal counter is used.
*/
- _unique: 0,
-
-
- //
- // Depreciated
- // The following properties are retained for backwards compatibility only.
- // The should not be used in new projects and will be removed in a future
- // version
- //
+ "sInstance": null,
/**
- * Version check function.
- * @type function
- * @depreciated Since 1.10
+ * tabindex attribute value that is added to DataTables control elements, allowing
+ * keyboard navigation of the table and its controls.
*/
- fnVersionCheck: DataTable.fnVersionCheck,
+ "iTabIndex": 0,
+ /**
+ * DIV container for the footer scrolling table if scrolling
+ */
+ "nScrollHead": null,
/**
- * Index for what 'this' index API functions should use
- * @type int
- * @deprecated Since v1.10
+ * DIV container for the footer scrolling table if scrolling
*/
- iApiIndex: 0,
+ "nScrollFoot": null,
+ /**
+ * Last applied sort
+ */
+ "aLastSort": [],
/**
- * jQuery UI class container
- * @type object
- * @deprecated Since v1.10
+ * Stored plug-in instances
*/
- oJUIClasses: {},
+ "oPlugins": {},
+ /**
+ * Function used to get a row's id from the row's data
+ */
+ "rowIdFn": null,
/**
- * Software version
- * @type string
- * @deprecated Since v1.10
+ * Data location where to store a row's id
*/
- sVersion: DataTable.version
+ "rowId": null,
+
+ caption: '',
+
+ captionNode: null,
+
+ colgroup: null
};
+ /**
+ * Extension object for DataTables that is used to provide all extension
+ * options.
+ *
+ * Note that the `DataTable.ext` object is available through
+ * `jQuery.fn.dataTable.ext` where it may be accessed and manipulated. It is
+ * also aliased to `jQuery.fn.dataTableExt` for historic reasons.
+ * @namespace
+ * @extends DataTable.models.ext
+ */
+
- //
- // Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts
- //
- $.extend( _ext, {
- afnFiltering: _ext.search,
- aTypes: _ext.type.detect,
- ofnSearch: _ext.type.search,
- oSort: _ext.type.order,
- afnSortData: _ext.order,
- aoFeatures: _ext.feature,
- oApi: _ext.internal,
- oStdClasses: _ext.classes,
- oPagination: _ext.pager
+ var extPagination = DataTable.ext.pager;
+
+ // Paging buttons configuration
+ $.extend( extPagination, {
+ simple: function () {
+ return [ 'previous', 'next' ];
+ },
+
+ full: function () {
+ return [ 'first', 'previous', 'next', 'last' ];
+ },
+
+ numbers: function () {
+ return [ 'numbers' ];
+ },
+
+ simple_numbers: function () {
+ return [ 'previous', 'numbers', 'next' ];
+ },
+
+ full_numbers: function () {
+ return [ 'first', 'previous', 'numbers', 'next', 'last' ];
+ },
+
+ first_last: function () {
+ return ['first', 'last'];
+ },
+
+ first_last_numbers: function () {
+ return ['first', 'numbers', 'last'];
+ },
+
+ // For testing and plug-ins to use
+ _numbers: _pagingNumbers,
+
+ // Number of number buttons - legacy, use `numbers` option for paging feature
+ numbers_length: 7
} );
- $.extend( DataTable.ext.classes, {
- "sTable": "dataTable",
- "sNoFooter": "no-footer",
-
- /* Paging buttons */
- "sPageButton": "paginate_button",
- "sPageButtonActive": "current",
- "sPageButtonDisabled": "disabled",
-
- /* Striping classes */
- "sStripeOdd": "odd",
- "sStripeEven": "even",
-
- /* Empty row */
- "sRowEmpty": "dataTables_empty",
-
- /* Features */
- "sWrapper": "dataTables_wrapper",
- "sFilter": "dataTables_filter",
- "sInfo": "dataTables_info",
- "sPaging": "dataTables_paginate paging_", /* Note that the type is postfixed */
- "sLength": "dataTables_length",
- "sProcessing": "dataTables_processing",
-
- /* Sorting */
- "sSortAsc": "sorting_asc",
- "sSortDesc": "sorting_desc",
- "sSortable": "sorting", /* Sortable in both directions */
- "sSortableAsc": "sorting_desc_disabled",
- "sSortableDesc": "sorting_asc_disabled",
- "sSortableNone": "sorting_disabled",
- "sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */
-
- /* Filtering */
- "sFilterInput": "",
-
- /* Page length */
- "sLengthSelect": "",
-
- /* Scrolling */
- "sScrollWrapper": "dataTables_scroll",
- "sScrollHead": "dataTables_scrollHead",
- "sScrollHeadInner": "dataTables_scrollHeadInner",
- "sScrollBody": "dataTables_scrollBody",
- "sScrollFoot": "dataTables_scrollFoot",
- "sScrollFootInner": "dataTables_scrollFootInner",
-
- /* Misc */
- "sHeaderTH": "",
- "sFooterTH": "",
-
- // Deprecated
- "sSortJUIAsc": "",
- "sSortJUIDesc": "",
- "sSortJUI": "",
- "sSortJUIAscAllowed": "",
- "sSortJUIDescAllowed": "",
- "sSortJUIWrapper": "",
- "sSortIcon": "",
- "sJUIHeader": "",
- "sJUIFooter": ""
+ $.extend( true, DataTable.ext.renderer, {
+ pagingButton: {
+ _: function (settings, buttonType, content, active, disabled) {
+ var classes = settings.oClasses.paging;
+ var btnClasses = [classes.button];
+ var btn;
+
+ if (active) {
+ btnClasses.push(classes.active);
+ }
+
+ if (disabled) {
+ btnClasses.push(classes.disabled)
+ }
+
+ if (buttonType === 'ellipsis') {
+ btn = $('<span class="ellipsis"></span>').html(content)[0];
+ }
+ else {
+ btn = $('<button>', {
+ class: btnClasses.join(' '),
+ role: 'link',
+ type: 'button'
+ }).html(content);
+ }
+
+ return {
+ display: btn,
+ clicker: btn
+ }
+ }
+ },
+
+ pagingContainer: {
+ _: function (settings, buttons) {
+ // No wrapping element - just append directly to the host
+ return buttons;
+ }
+ }
} );
+ // Common function to remove new lines, strip HTML and diacritic control
+ var _filterString = function (stripHtml, normalize) {
+ return function (str) {
+ if (_empty(str) || typeof str !== 'string') {
+ return str;
+ }
+
+ str = str.replace( _re_new_lines, " " );
+
+ if (stripHtml) {
+ str = _stripHtml(str);
+ }
+
+ if (normalize) {
+ str = _normalize(str, false);
+ }
+
+ return str;
+ };
+ }
+
+ /*
+ * Public helper functions. These aren't used internally by DataTables, or
+ * called by any of the options passed into DataTables, but they can be used
+ * externally by developers working with DataTables. They are helper functions
+ * to make working with DataTables a little bit easier.
+ */
+
+ function __mldFnName(name) {
+ return name.replace(/[\W]/g, '_')
+ }
+
+ // Common logic for moment, luxon or a date action
+ function __mld( dt, momentFn, luxonFn, dateFn, arg1 ) {
+ if (window.moment) {
+ return dt[momentFn]( arg1 );
+ }
+ else if (window.luxon) {
+ return dt[luxonFn]( arg1 );
+ }
+
+ return dateFn ? dt[dateFn]( arg1 ) : dt;
+ }
+
+
+ var __mlWarning = false;
+ function __mldObj (d, format, locale) {
+ var dt;
+
+ if (window.moment) {
+ dt = window.moment.utc( d, format, locale, true );
+
+ if (! dt.isValid()) {
+ return null;
+ }
+ }
+ else if (window.luxon) {
+ dt = format && typeof d === 'string'
+ ? window.luxon.DateTime.fromFormat( d, format )
+ : window.luxon.DateTime.fromISO( d );
+
+ if (! dt.isValid) {
+ return null;
+ }
+
+ dt.setLocale(locale);
+ }
+ else if (! format) {
+ // No format given, must be ISO
+ dt = new Date(d);
+ }
+ else {
+ if (! __mlWarning) {
+ alert('DataTables warning: Formatted date without Moment.js or Luxon - https://datatables.net/tn/17');
+ }
+
+ __mlWarning = true;
+ }
+
+ return dt;
+ }
+
+ // Wrapper for date, datetime and time which all operate the same way with the exception of
+ // the output string for auto locale support
+ function __mlHelper (localeString) {
+ return function ( from, to, locale, def ) {
+ // Luxon and Moment support
+ // Argument shifting
+ if ( arguments.length === 0 ) {
+ locale = 'en';
+ to = null; // means toLocaleString
+ from = null; // means iso8601
+ }
+ else if ( arguments.length === 1 ) {
+ locale = 'en';
+ to = from;
+ from = null;
+ }
+ else if ( arguments.length === 2 ) {
+ locale = to;
+ to = from;
+ from = null;
+ }
+
+ var typeName = 'datetime' + (to ? '-' + __mldFnName(to) : '');
+
+ // Add type detection and sorting specific to this date format - we need to be able to identify
+ // date type columns as such, rather than as numbers in extensions. Hence the need for this.
+ if (! DataTable.ext.type.order[typeName]) {
+ DataTable.type(typeName, {
+ detect: function (d) {
+ // The renderer will give the value to type detect as the type!
+ return d === typeName ? typeName : false;
+ },
+ order: {
+ pre: function (d) {
+ // The renderer gives us Moment, Luxon or Date obects for the sorting, all of which have a
+ // `valueOf` which gives milliseconds epoch
+ return d.valueOf();
+ }
+ },
+ className: 'dt-right'
+ });
+ }
+
+ return function ( d, type ) {
+ // Allow for a default value
+ if (d === null || d === undefined) {
+ if (def === '--now') {
+ // We treat everything as UTC further down, so no changes are
+ // made, as such need to get the local date / time as if it were
+ // UTC
+ var local = new Date();
+ d = new Date( Date.UTC(
+ local.getFullYear(), local.getMonth(), local.getDate(),
+ local.getHours(), local.getMinutes(), local.getSeconds()
+ ) );
+ }
+ else {
+ d = '';
+ }
+ }
+
+ if (type === 'type') {
+ // Typing uses the type name for fast matching
+ return typeName;
+ }
+
+ if (d === '') {
+ return type !== 'sort'
+ ? ''
+ : __mldObj('0000-01-01 00:00:00', null, locale);
+ }
+
+ // Shortcut. If `from` and `to` are the same, we are using the renderer to
+ // format for ordering, not display - its already in the display format.
+ if ( to !== null && from === to && type !== 'sort' && type !== 'type' && ! (d instanceof Date) ) {
+ return d;
+ }
+
+ var dt = __mldObj(d, from, locale);
- var extPagination = DataTable.ext.pager;
+ if (dt === null) {
+ return d;
+ }
- function _numbers ( page, pages ) {
- var
- numbers = [],
- buttons = extPagination.numbers_length,
- half = Math.floor( buttons / 2 ),
- i = 1;
+ if (type === 'sort') {
+ return dt;
+ }
+
+ var formatted = to === null
+ ? __mld(dt, 'toDate', 'toJSDate', '')[localeString]()
+ : __mld(dt, 'format', 'toFormat', 'toISOString', to);
- if ( pages <= buttons ) {
- numbers = _range( 0, pages );
- }
- else if ( page <= half ) {
- numbers = _range( 0, buttons-2 );
- numbers.push( 'ellipsis' );
- numbers.push( pages-1 );
+ // XSS protection
+ return type === 'display' ?
+ _escapeHtml( formatted ) :
+ formatted;
+ };
}
- else if ( page >= pages - 1 - half ) {
- numbers = _range( pages-(buttons-2), pages );
- numbers.splice( 0, 0, 'ellipsis' ); // no unshift in ie6
- numbers.splice( 0, 0, 0 );
+ }
+
+ // Based on locale, determine standard number formatting
+ // Fallback for legacy browsers is US English
+ var __thousands = ',';
+ var __decimal = '.';
+
+ if (window.Intl !== undefined) {
+ try {
+ var num = new Intl.NumberFormat().formatToParts(100000.1);
+
+ for (var i=0 ; i<num.length ; i++) {
+ if (num[i].type === 'group') {
+ __thousands = num[i].value;
+ }
+ else if (num[i].type === 'decimal') {
+ __decimal = num[i].value;
+ }
+ }
}
- else {
- numbers = _range( page-half+2, page+half-1 );
- numbers.push( 'ellipsis' );
- numbers.push( pages-1 );
- numbers.splice( 0, 0, 'ellipsis' );
- numbers.splice( 0, 0, 0 );
+ catch (e) {
+ // noop
}
-
- numbers.DT_el = 'span';
- return numbers;
}
+ // Formatted date time detection - use by declaring the formats you are going to use
+ DataTable.datetime = function ( format, locale ) {
+ var typeName = 'datetime-detect-' + __mldFnName(format);
- $.extend( extPagination, {
- simple: function ( page, pages ) {
- return [ 'previous', 'next' ];
- },
+ if (! locale) {
+ locale = 'en';
+ }
- full: function ( page, pages ) {
- return [ 'first', 'previous', 'next', 'last' ];
- },
+ if (! DataTable.ext.type.order[typeName]) {
+ DataTable.type(typeName, {
+ detect: function (d) {
+ var dt = __mldObj(d, format, locale);
+ return d === '' || dt ? typeName : false;
+ },
+ order: {
+ pre: function (d) {
+ return __mldObj(d, format, locale) || 0;
+ }
+ },
+ className: 'dt-right'
+ });
+ }
+ }
- numbers: function ( page, pages ) {
- return [ _numbers(page, pages) ];
- },
+ /**
+ * Helpers for `columns.render`.
+ *
+ * The options defined here can be used with the `columns.render` initialisation
+ * option to provide a display renderer. The following functions are defined:
+ *
+ * * `moment` - Uses the MomentJS library to convert from a given format into another.
+ * This renderer has three overloads:
+ * * 1 parameter:
+ * * `string` - Format to convert to (assumes input is ISO8601 and locale is `en`)
+ * * 2 parameters:
+ * * `string` - Format to convert from
+ * * `string` - Format to convert to. Assumes `en` locale
+ * * 3 parameters:
+ * * `string` - Format to convert from
+ * * `string` - Format to convert to
+ * * `string` - Locale
+ * * `number` - Will format numeric data (defined by `columns.data`) for
+ * display, retaining the original unformatted data for sorting and filtering.
+ * It takes 5 parameters:
+ * * `string` - Thousands grouping separator
+ * * `string` - Decimal point indicator
+ * * `integer` - Number of decimal points to show
+ * * `string` (optional) - Prefix.
+ * * `string` (optional) - Postfix (/suffix).
+ * * `text` - Escape HTML to help prevent XSS attacks. It has no optional
+ * parameters.
+ *
+ * @example
+ * // Column definition using the number renderer
+ * {
+ * data: "salary",
+ * render: $.fn.dataTable.render.number( '\'', '.', 0, '$' )
+ * }
+ *
+ * @namespace
+ */
+ DataTable.render = {
+ date: __mlHelper('toLocaleDateString'),
+ datetime: __mlHelper('toLocaleString'),
+ time: __mlHelper('toLocaleTimeString'),
+ number: function ( thousands, decimal, precision, prefix, postfix ) {
+ // Auto locale detection
+ if (thousands === null || thousands === undefined) {
+ thousands = __thousands;
+ }
- simple_numbers: function ( page, pages ) {
- return [ 'previous', _numbers(page, pages), 'next' ];
- },
+ if (decimal === null || decimal === undefined) {
+ decimal = __decimal;
+ }
- full_numbers: function ( page, pages ) {
- return [ 'first', 'previous', _numbers(page, pages), 'next', 'last' ];
- },
-
- first_last_numbers: function (page, pages) {
- return ['first', _numbers(page, pages), 'last'];
- },
+ return {
+ display: function ( d ) {
+ if ( typeof d !== 'number' && typeof d !== 'string' ) {
+ return d;
+ }
- // For testing and plug-ins to use
- _numbers: _numbers,
+ if (d === '' || d === null) {
+ return d;
+ }
- // Number of number buttons (including ellipsis) to show. _Must be odd!_
- numbers_length: 7
- } );
+ var negative = d < 0 ? '-' : '';
+ var flo = parseFloat( d );
+ var abs = Math.abs(flo);
+ // Scientific notation for large and small numbers
+ if (abs >= 100000000000 || (abs < 0.0001 && abs !== 0) ) {
+ var exp = flo.toExponential(precision).split(/e\+?/);
+ return exp[0] + ' x 10<sup>' + exp[1] + '</sup>';
+ }
- $.extend( true, DataTable.ext.renderer, {
- pageButton: {
- _: function ( settings, host, idx, buttons, page, pages ) {
- var classes = settings.oClasses;
- var lang = settings.oLanguage.oPaginate;
- var aria = settings.oLanguage.oAria.paginate || {};
- var btnDisplay, btnClass;
-
- var attach = function( container, buttons ) {
- var i, ien, node, button;
- var disabledClass = classes.sPageButtonDisabled;
- var clickHandler = function ( e ) {
- _fnPageChange( settings, e.data.action, true );
- };
+ // If NaN then there isn't much formatting that we can do - just
+ // return immediately, escaping any HTML (this was supposed to
+ // be a number after all)
+ if ( isNaN( flo ) ) {
+ return _escapeHtml( d );
+ }
- for ( i=0, ien=buttons.length ; i<ien ; i++ ) {
- button = buttons[i];
+ flo = flo.toFixed( precision );
+ d = Math.abs( flo );
- if ( Array.isArray( button ) ) {
- var inner = $( '<'+(button.DT_el || 'div')+'/>' )
- .appendTo( container );
- attach( inner, button );
- }
- else {
- var disabled = false;
+ var intPart = parseInt( d, 10 );
+ var floatPart = precision ?
+ decimal+(d - intPart).toFixed( precision ).substring( 2 ):
+ '';
- btnDisplay = null;
- btnClass = button;
+ // If zero, then can't have a negative prefix
+ if (intPart === 0 && parseFloat(floatPart) === 0) {
+ negative = '';
+ }
- switch ( button ) {
- case 'ellipsis':
- container.append('<span class="ellipsis">…</span>');
- break;
+ return negative + (prefix||'') +
+ intPart.toString().replace(
+ /\B(?=(\d{3})+(?!\d))/g, thousands
+ ) +
+ floatPart +
+ (postfix||'');
+ }
+ };
+ },
- case 'first':
- btnDisplay = lang.sFirst;
+ text: function () {
+ return {
+ display: _escapeHtml,
+ filter: _escapeHtml
+ };
+ }
+ };
- if ( page === 0 ) {
- disabled = true;
- }
- break;
- case 'previous':
- btnDisplay = lang.sPrevious;
+ var _extTypes = DataTable.ext.type;
- if ( page === 0 ) {
- disabled = true;
- }
- break;
+ // Get / set type
+ DataTable.type = function (name, prop, val) {
+ if (! prop) {
+ return {
+ className: _extTypes.className[name],
+ detect: _extTypes.detect.find(function (fn) {
+ return fn.name === name;
+ }),
+ order: {
+ pre: _extTypes.order[name + '-pre'],
+ asc: _extTypes.order[name + '-asc'],
+ desc: _extTypes.order[name + '-desc']
+ },
+ render: _extTypes.render[name],
+ search: _extTypes.search[name]
+ };
+ }
- case 'next':
- btnDisplay = lang.sNext;
+ var setProp = function(prop, propVal) {
+ _extTypes[prop][name] = propVal;
+ };
+ var setDetect = function (fn) {
+ // Wrap to allow the function to return `true` rather than
+ // specifying the type name.
+ var cb = function (d, s) {
+ var ret = fn(d, s);
+
+ return ret === true
+ ? name
+ : ret;
+ };
+ Object.defineProperty(cb, "name", {value: name});
- if ( pages === 0 || page === pages-1 ) {
- disabled = true;
- }
- break;
+ var idx = _extTypes.detect.findIndex(function (fn) {
+ return fn.name === name;
+ });
- case 'last':
- btnDisplay = lang.sLast;
+ if (idx === -1) {
+ _extTypes.detect.unshift(cb);
+ }
+ else {
+ _extTypes.detect.splice(idx, 1, cb);
+ }
+ };
+ var setOrder = function (obj) {
+ _extTypes.order[name + '-pre'] = obj.pre; // can be undefined
+ _extTypes.order[name + '-asc'] = obj.asc; // can be undefined
+ _extTypes.order[name + '-desc'] = obj.desc; // can be undefined
+ };
- if ( pages === 0 || page === pages-1 ) {
- disabled = true;
- }
- break;
+ // prop is optional
+ if (val === undefined) {
+ val = prop;
+ prop = null;
+ }
- default:
- btnDisplay = settings.fnFormatNumber( button + 1 );
- btnClass = page === button ?
- classes.sPageButtonActive : '';
- break;
- }
+ if (prop === 'className') {
+ setProp('className', val);
+ }
+ else if (prop === 'detect') {
+ setDetect(val);
+ }
+ else if (prop === 'order') {
+ setOrder(val);
+ }
+ else if (prop === 'render') {
+ setProp('render', val);
+ }
+ else if (prop === 'search') {
+ setProp('search', val);
+ }
+ else if (! prop) {
+ if (val.className) {
+ setProp('className', val.className);
+ }
- if ( btnDisplay !== null ) {
- var tag = settings.oInit.pagingTag || 'a';
+ if (val.detect !== undefined) {
+ setDetect(val.detect);
+ }
- if (disabled) {
- btnClass += ' ' + disabledClass;
- }
+ if (val.order) {
+ setOrder(val.order);
+ }
- node = $('<'+tag+'>', {
- 'class': classes.sPageButton+' '+btnClass,
- 'aria-controls': settings.sTableId,
- 'aria-disabled': disabled ? 'true' : null,
- 'aria-label': aria[ button ],
- 'role': 'link',
- 'aria-current': btnClass === classes.sPageButtonActive ? 'page' : null,
- 'data-dt-idx': button,
- 'tabindex': disabled ? -1 : settings.iTabIndex,
- 'id': idx === 0 && typeof button === 'string' ?
- settings.sTableId +'_'+ button :
- null
- } )
- .html( btnDisplay )
- .appendTo( container );
-
- _fnBindAction(
- node, {action: button}, clickHandler
- );
- }
- }
- }
- };
+ if (val.render !== undefined) {
+ setProp('render', val.render);
+ }
- // IE9 throws an 'unknown error' if document.activeElement is used
- // inside an iframe or frame. Try / catch the error. Not good for
- // accessibility, but neither are frames.
- var activeEl;
+ if (val.search !== undefined) {
+ setProp('search', val.search);
+ }
+ }
+ }
- try {
- // Because this approach is destroying and recreating the paging
- // elements, focus is lost on the select button which is bad for
- // accessibility. So we want to restore focus once the draw has
- // completed
- activeEl = $(host).find(document.activeElement).data('dt-idx');
- }
- catch (e) {}
+ // Get a list of types
+ DataTable.types = function () {
+ return _extTypes.detect.map(function (fn) {
+ return fn.name;
+ });
+ };
- attach( $(host).empty(), buttons );
+ //
+ // Built in data types
+ //
- if ( activeEl !== undefined ) {
- $(host).find( '[data-dt-idx='+activeEl+']' ).trigger('focus');
- }
+ DataTable.type('string', {
+ detect: function () {
+ return 'string';
+ },
+ order: {
+ pre: function ( a ) {
+ // This is a little complex, but faster than always calling toString,
+ // http://jsperf.com/tostring-v-check
+ return _empty(a) ?
+ '' :
+ typeof a === 'string' ?
+ a.toLowerCase() :
+ ! a.toString ?
+ '' :
+ a.toString();
}
- }
- } );
-
+ },
+ search: _filterString(false, true)
+ });
- // Built in type detection. See model.ext.aTypes for information about
- // what is required from this methods.
- $.extend( DataTable.ext.type.detect, [
- // Plain numbers - first since V8 detects some plain numbers as dates
- // e.g. Date.parse('55') (but not all, e.g. Date.parse('22')...).
- function ( d, settings )
- {
- var decimal = settings.oLanguage.sDecimal;
- return _isNumber( d, decimal ) ? 'num'+decimal : null;
+ DataTable.type('html', {
+ detect: function ( d ) {
+ return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1) ?
+ 'html' : null;
+ },
+ order: {
+ pre: function ( a ) {
+ return _empty(a) ?
+ '' :
+ a.replace ?
+ _stripHtml(a).trim().toLowerCase() :
+ a+'';
+ }
},
+ search: _filterString(true, true)
+ });
+
- // Dates (only those recognised by the browser's Date.parse)
- function ( d, settings )
+ DataTable.type('date', {
+ className: 'dt-type-date',
+ detect: function ( d )
{
// V8 tries _very_ hard to make a string passed into `Date.parse()`
// valid, so we need to use a regex to restrict date formats. Use a
@@ -14999,65 +12320,80 @@
var parsed = Date.parse(d);
return (parsed !== null && !isNaN(parsed)) || _empty(d) ? 'date' : null;
},
+ order: {
+ pre: function ( d ) {
+ var ts = Date.parse( d );
+ return isNaN(ts) ? -Infinity : ts;
+ }
+ }
+ });
+
- // Formatted numbers
- function ( d, settings )
+ DataTable.type('html-num-fmt', {
+ className: 'dt-type-numeric',
+ detect: function ( d, settings )
{
var decimal = settings.oLanguage.sDecimal;
- return _isNumber( d, decimal, true ) ? 'num-fmt'+decimal : null;
+ return _htmlNumeric( d, decimal, true ) ? 'html-num-fmt' : null;
+ },
+ order: {
+ pre: function ( d, s ) {
+ var dp = s.oLanguage.sDecimal;
+ return __numericReplace( d, dp, _re_html, _re_formatted_numeric );
+ }
},
+ search: _filterString(true, true)
+ });
+
- // HTML numeric
- function ( d, settings )
+ DataTable.type('html-num', {
+ className: 'dt-type-numeric',
+ detect: function ( d, settings )
{
var decimal = settings.oLanguage.sDecimal;
- return _htmlNumeric( d, decimal ) ? 'html-num'+decimal : null;
+ return _htmlNumeric( d, decimal ) ? 'html-num' : null;
+ },
+ order: {
+ pre: function ( d, s ) {
+ var dp = s.oLanguage.sDecimal;
+ return __numericReplace( d, dp, _re_html );
+ }
},
+ search: _filterString(true, true)
+ });
+
- // HTML numeric, formatted
- function ( d, settings )
+ DataTable.type('num-fmt', {
+ className: 'dt-type-numeric',
+ detect: function ( d, settings )
{
var decimal = settings.oLanguage.sDecimal;
- return _htmlNumeric( d, decimal, true ) ? 'html-num-fmt'+decimal : null;
+ return _isNumber( d, decimal, true ) ? 'num-fmt' : null;
},
-
- // HTML (this is strict checking - there must be html)
- function ( d, settings )
- {
- return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1) ?
- 'html' : null;
+ order: {
+ pre: function ( d, s ) {
+ var dp = s.oLanguage.sDecimal;
+ return __numericReplace( d, dp, _re_formatted_numeric );
+ }
}
- ] );
-
-
-
- // Filter formatting functions. See model.ext.ofnSearch for information about
- // what is required from these methods.
- //
- // Note that additional search methods are added for the html numbers and
- // html formatted numbers by `_addNumericSort()` when we know what the decimal
- // place is
+ });
- $.extend( DataTable.ext.type.search, {
- html: function ( data ) {
- return _empty(data) ?
- data :
- typeof data === 'string' ?
- data
- .replace( _re_new_lines, " " )
- .replace( _re_html, "" ) :
- '';
+ DataTable.type('num', {
+ className: 'dt-type-numeric',
+ detect: function ( d, settings )
+ {
+ var decimal = settings.oLanguage.sDecimal;
+ return _isNumber( d, decimal ) ? 'num' : null;
},
-
- string: function ( data ) {
- return _empty(data) ?
- data :
- typeof data === 'string' ?
- data.replace( _re_new_lines, " " ) :
- data;
+ order: {
+ pre: function (d, s) {
+ var dp = s.oLanguage.sDecimal;
+ return __numericReplace( d, dp );
+ }
}
- } );
+ });
+
@@ -15093,609 +12429,733 @@
};
- // Add the numeric 'deformatting' functions for sorting and search. This is done
- // in a function to provide an easy ability for the language options to add
- // additional methods if a non-period decimal place is used.
- function _addNumericSort ( decimalPlace ) {
- $.each(
- {
- // Plain numbers
- "num": function ( d ) {
- return __numericReplace( d, decimalPlace );
- },
-
- // Formatted numbers
- "num-fmt": function ( d ) {
- return __numericReplace( d, decimalPlace, _re_formatted_numeric );
- },
-
- // HTML numeric
- "html-num": function ( d ) {
- return __numericReplace( d, decimalPlace, _re_html );
- },
-
- // HTML numeric, formatted
- "html-num-fmt": function ( d ) {
- return __numericReplace( d, decimalPlace, _re_html, _re_formatted_numeric );
- }
- },
- function ( key, fn ) {
- // Add the ordering method
- _ext.type.order[ key+decimalPlace+'-pre' ] = fn;
-
- // For HTML types add a search formatter that will strip the HTML
- if ( key.match(/^html\-/) ) {
- _ext.type.search[ key+decimalPlace ] = _ext.type.search.html;
- }
+ $.extend( true, DataTable.ext.renderer, {
+ footer: {
+ _: function ( settings, cell, classes ) {
+ cell.addClass(classes.tfoot.cell);
}
- );
- }
-
-
- // Default sort methods
- $.extend( _ext.type.order, {
- // Dates
- "date-pre": function ( d ) {
- var ts = Date.parse( d );
- return isNaN(ts) ? -Infinity : ts;
- },
-
- // html
- "html-pre": function ( a ) {
- return _empty(a) ?
- '' :
- a.replace ?
- a.replace( /<.*?>/g, "" ).toLowerCase() :
- a+'';
- },
-
- // string
- "string-pre": function ( a ) {
- // This is a little complex, but faster than always calling toString,
- // http://jsperf.com/tostring-v-check
- return _empty(a) ?
- '' :
- typeof a === 'string' ?
- a.toLowerCase() :
- ! a.toString ?
- '' :
- a.toString();
- },
-
- // string-asc and -desc are retained only for compatibility with the old
- // sort methods
- "string-asc": function ( x, y ) {
- return ((x < y) ? -1 : ((x > y) ? 1 : 0));
},
- "string-desc": function ( x, y ) {
- return ((x < y) ? 1 : ((x > y) ? -1 : 0));
- }
- } );
+ header: {
+ _: function ( settings, cell, classes ) {
+ cell.addClass(classes.thead.cell);
+ if (! settings.oFeatures.bSort) {
+ cell.addClass(classes.order.none);
+ }
- // Numeric sorting types - order doesn't matter here
- _addNumericSort( '' );
+ var legacyTop = settings.bSortCellsTop;
+ var headerRows = cell.closest('thead').find('tr');
+ var rowIdx = cell.parent().index();
+ // Conditions to not apply the ordering icons
+ if (
+ // Cells and rows which have the attribute to disable the icons
+ cell.attr('data-dt-order') === 'disable' ||
+ cell.parent().attr('data-dt-order') === 'disable' ||
+
+ // Legacy support for `orderCellsTop`. If it is set, then cells
+ // which are not in the top or bottom row of the header (depending
+ // on the value) do not get the sorting classes applied to them
+ (legacyTop === true && rowIdx !== 0) ||
+ (legacyTop === false && rowIdx !== headerRows.length - 1)
+ ) {
+ return;
+ }
- $.extend( true, DataTable.ext.renderer, {
- header: {
- _: function ( settings, cell, column, classes ) {
// No additional mark-up required
// Attach a sort listener to update on sort - note that using the
// `DT` namespace will allow the event to be removed automatically
// on destroy, while the `dt` namespaced event is the one we are
// listening for
- $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) {
+ $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting ) {
if ( settings !== ctx ) { // need to check this this is the host
return; // table, not a nested one
}
- var colIdx = column.idx;
+ var orderClasses = classes.order;
+ var columns = ctx.api.columns( cell );
+ var col = settings.aoColumns[columns.flatten()[0]];
+ var orderable = columns.orderable().includes(true);
+ var ariaType = '';
+ var indexes = columns.indexes();
+ var sortDirs = columns.orderable(true).flatten();
+ var orderedColumns = ',' + sorting.map( function (val) {
+ return val.col;
+ } ).join(',') + ',';
cell
.removeClass(
- classes.sSortAsc +' '+
- classes.sSortDesc
+ orderClasses.isAsc +' '+
+ orderClasses.isDesc
)
- .addClass( columns[ colIdx ] == 'asc' ?
- classes.sSortAsc : columns[ colIdx ] == 'desc' ?
- classes.sSortDesc :
- column.sSortingClass
+ .toggleClass( orderClasses.none, ! orderable )
+ .toggleClass( orderClasses.canAsc, orderable && sortDirs.includes('asc') )
+ .toggleClass( orderClasses.canDesc, orderable && sortDirs.includes('desc') );
+
+ var sortIdx = orderedColumns.indexOf( ',' + indexes.toArray().join(',') + ',' );
+
+ if ( sortIdx !== -1 ) {
+ // Get the ordering direction for the columns under this cell
+ // Note that it is possible for a cell to be asc and desc sorting
+ // (column spanning cells)
+ var orderDirs = columns.order();
+
+ cell.addClass(
+ orderDirs.includes('asc') ? orderClasses.isAsc : '' +
+ orderDirs.includes('desc') ? orderClasses.isDesc : ''
);
- } );
- },
+ }
- jqueryui: function ( settings, cell, column, classes ) {
- $('<div/>')
- .addClass( classes.sSortJUIWrapper )
- .append( cell.contents() )
- .append( $('<span/>')
- .addClass( classes.sSortIcon+' '+column.sSortingClassJUI )
- )
- .appendTo( cell );
+ // The ARIA spec says that only one column should be marked with aria-sort
+ if ( sortIdx === 0 ) {
+ var firstSort = sorting[0];
+ var sortOrder = col.asSorting;
- // Attach a sort listener to update on sort
- $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) {
- if ( settings !== ctx ) {
- return;
+ cell.attr('aria-sort', firstSort.dir === 'asc' ? 'ascending' : 'descending');
+
+ // Determine if the next click will remove sorting or change the sort
+ ariaType = ! sortOrder[firstSort.index + 1] ? 'Remove' : 'Reverse';
+ }
+ else {
+ cell.removeAttr('aria-sort');
}
- var colIdx = column.idx;
+ cell.attr('aria-label', orderable
+ ? col.ariaTitle + ctx.api.i18n('oAria.orderable' + ariaType)
+ : col.ariaTitle
+ );
- cell
- .removeClass( classes.sSortAsc +" "+classes.sSortDesc )
- .addClass( columns[ colIdx ] == 'asc' ?
- classes.sSortAsc : columns[ colIdx ] == 'desc' ?
- classes.sSortDesc :
- column.sSortingClass
- );
+ if (orderable) {
+ cell.find('.dt-column-title').attr('role', 'button');
+ cell.attr('tabindex', 0)
+ }
+ } );
+ }
+ },
- cell
- .find( 'span.'+classes.sSortIcon )
- .removeClass(
- classes.sSortJUIAsc +" "+
- classes.sSortJUIDesc +" "+
- classes.sSortJUI +" "+
- classes.sSortJUIAscAllowed +" "+
- classes.sSortJUIDescAllowed
- )
- .addClass( columns[ colIdx ] == 'asc' ?
- classes.sSortJUIAsc : columns[ colIdx ] == 'desc' ?
- classes.sSortJUIDesc :
- column.sSortingClassJUI
- );
+ layout: {
+ _: function ( settings, container, items ) {
+ var row = $('<div/>')
+ .addClass('dt-layout-row')
+ .appendTo( container );
+
+ $.each( items, function (key, val) {
+ var klass = ! val.table ?
+ 'dt-'+key+' ' :
+ '';
+
+ if (val.table) {
+ row.addClass('dt-layout-table');
+ }
+
+ $('<div/>')
+ .attr({
+ id: val.id || null,
+ "class": 'dt-layout-cell '+klass+(val.className || '')
+ })
+ .append( val.contents )
+ .appendTo( row );
} );
}
}
} );
- /*
- * Public helper functions. These aren't used internally by DataTables, or
- * called by any of the options passed into DataTables, but they can be used
- * externally by developers working with DataTables. They are helper functions
- * to make working with DataTables a little bit easier.
- */
- var __htmlEscapeEntities = function ( d ) {
- if (Array.isArray(d)) {
- d = d.join(',');
- }
+ DataTable.feature = {};
- return typeof d === 'string' ?
- d
- .replace(/&/g, '&')
- .replace(/</g, '<')
- .replace(/>/g, '>')
- .replace(/"/g, '"') :
- d;
+ // Third parameter is internal only!
+ DataTable.feature.register = function ( name, cb, legacy ) {
+ DataTable.ext.features[ name ] = cb;
+
+ if (legacy) {
+ _ext.feature.push({
+ cFeature: legacy,
+ fnInit: cb
+ });
+ }
};
- // Common logic for moment, luxon or a date action
- function __mld( dt, momentFn, luxonFn, dateFn, arg1 ) {
- if (window.moment) {
- return dt[momentFn]( arg1 );
+ DataTable.feature.register( 'info', function ( settings, opts ) {
+ // For compatibility with the legacy `info` top level option
+ if (! settings.oFeatures.bInfo) {
+ return null;
}
- else if (window.luxon) {
- return dt[luxonFn]( arg1 );
+
+ var
+ lang = settings.oLanguage,
+ tid = settings.sTableId,
+ n = $('<div/>', {
+ 'class': settings.oClasses.info.container,
+ } );
+
+ opts = $.extend({
+ callback: lang.fnInfoCallback,
+ empty: lang.sInfoEmpty,
+ postfix: lang.sInfoPostFix,
+ search: lang.sInfoFiltered,
+ text: lang.sInfo,
+ }, opts);
+
+
+ // Update display on each draw
+ settings.aoDrawCallback.push(function (s) {
+ _fnUpdateInfo(s, opts, n);
+ });
+
+ // For the first info display in the table, we add a callback and aria information.
+ if (! settings._infoEl) {
+ n.attr({
+ 'aria-live': 'polite',
+ id: tid+'_info',
+ role: 'status'
+ });
+
+ // Table is described by our info div
+ $(settings.nTable).attr( 'aria-describedby', tid+'_info' );
+
+ settings._infoEl = n;
}
-
- return dateFn ? dt[dateFn]( arg1 ) : dt;
- }
+ return n;
+ }, 'i' );
- var __mlWarning = false;
- function __mldObj (d, format, locale) {
- var dt;
+ /**
+ * Update the information elements in the display
+ * @param {object} settings dataTables settings object
+ * @memberof DataTable#oApi
+ */
+ function _fnUpdateInfo ( settings, opts, node )
+ {
+ var
+ start = settings._iDisplayStart+1,
+ end = settings.fnDisplayEnd(),
+ max = settings.fnRecordsTotal(),
+ total = settings.fnRecordsDisplay(),
+ out = total
+ ? opts.text
+ : opts.empty;
- if (window.moment) {
- dt = window.moment.utc( d, format, locale, true );
+ if ( total !== max ) {
+ // Record set after filtering
+ out += ' ' + opts.search;
+ }
- if (! dt.isValid()) {
- return null;
- }
+ // Convert the macros
+ out += opts.postfix;
+ out = _fnMacros( settings, out );
+
+ if ( opts.callback ) {
+ out = opts.callback.call( settings.oInstance,
+ settings, start, end, max, total, out
+ );
}
- else if (window.luxon) {
- dt = format && typeof d === 'string'
- ? window.luxon.DateTime.fromFormat( d, format )
- : window.luxon.DateTime.fromISO( d );
- if (! dt.isValid) {
- return null;
- }
+ node.html( out );
- dt.setLocale(locale);
+ _fnCallbackFire(settings, null, 'info', [settings, node[0], out]);
+ }
+
+ var __searchCounter = 0;
+
+ // opts
+ // - text
+ // - placeholder
+ DataTable.feature.register( 'search', function ( settings, opts ) {
+ // Don't show the input if filtering isn't available on the table
+ if (! settings.oFeatures.bFilter) {
+ return null;
}
- else if (! format) {
- // No format given, must be ISO
- dt = new Date(d);
+
+ var classes = settings.oClasses.search;
+ var tableId = settings.sTableId;
+ var language = settings.oLanguage;
+ var previousSearch = settings.oPreviousSearch;
+ var input = '<input type="search" class="'+classes.input+'"/>';
+
+ opts = $.extend({
+ placeholder: language.sSearchPlaceholder,
+ text: language.sSearch
+ }, opts);
+
+ // The _INPUT_ is optional - is appended if not present
+ if (opts.text.indexOf('_INPUT_') === -1) {
+ opts.text += '_INPUT_';
}
- else {
- if (! __mlWarning) {
- alert('DataTables warning: Formatted date without Moment.js or Luxon - https://datatables.net/tn/17');
- }
- __mlWarning = true;
+ opts.text = _fnMacros(settings, opts.text);
+
+ // We can put the <input> outside of the label if it is at the start or end
+ // which helps improve accessability (not all screen readers like implicit
+ // for elements).
+ var end = opts.text.match(/_INPUT_$/);
+ var start = opts.text.match(/^_INPUT_/);
+ var removed = opts.text.replace(/_INPUT_/, '');
+ var str = '<label>' + opts.text + '</label>';
+
+ if (start) {
+ str = '_INPUT_<label>' + removed + '</label>';
+ }
+ else if (end) {
+ str = '<label>' + removed + '</label>_INPUT_';
}
- return dt;
- }
+ var filter = $('<div>')
+ .addClass(classes.container)
+ .append(str.replace(/_INPUT_/, input));
- // Wrapper for date, datetime and time which all operate the same way with the exception of
- // the output string for auto locale support
- function __mlHelper (localeString) {
- return function ( from, to, locale, def ) {
- // Luxon and Moment support
- // Argument shifting
- if ( arguments.length === 0 ) {
- locale = 'en';
- to = null; // means toLocaleString
- from = null; // means iso8601
+ // add for and id to label and input
+ filter.find('label').attr('for', 'dt-search-' + __searchCounter);
+ filter.find('input').attr('id', 'dt-search-' + __searchCounter);
+ __searchCounter++;
+
+ var searchFn = function(event) {
+ var val = this.value;
+
+ if(previousSearch.return && event.key !== "Enter") {
+ return;
}
- else if ( arguments.length === 1 ) {
- locale = 'en';
- to = from;
- from = null;
+
+ /* Now do the filter */
+ if ( val != previousSearch.search ) {
+ previousSearch.search = val;
+
+ _fnFilterComplete( settings, previousSearch );
+
+ // Need to redraw, without resorting
+ settings._iDisplayStart = 0;
+ _fnDraw( settings );
}
- else if ( arguments.length === 2 ) {
- locale = to;
- to = from;
- from = null;
+ };
+
+ var searchDelay = settings.searchDelay !== null ?
+ settings.searchDelay :
+ 0;
+
+ var jqFilter = $('input', filter)
+ .val( previousSearch.search )
+ .attr( 'placeholder', opts.placeholder )
+ .on(
+ 'keyup.DT search.DT input.DT paste.DT cut.DT',
+ searchDelay ?
+ DataTable.util.debounce( searchFn, searchDelay ) :
+ searchFn
+ )
+ .on( 'mouseup.DT', function(e) {
+ // Edge fix! Edge 17 does not trigger anything other than mouse events when clicking
+ // on the clear icon (Edge bug 17584515). This is safe in other browsers as `searchFn`
+ // checks the value to see if it has changed. In other browsers it won't have.
+ setTimeout( function () {
+ searchFn.call(jqFilter[0], e);
+ }, 10);
+ } )
+ .on( 'keypress.DT', function(e) {
+ /* Prevent form submission */
+ if ( e.keyCode == 13 ) {
+ return false;
+ }
+ } )
+ .attr('aria-controls', tableId);
+
+ // Update the input elements whenever the table is filtered
+ $(settings.nTable).on( 'search.dt.DT', function ( ev, s ) {
+ if ( settings === s && jqFilter[0] !== document.activeElement ) {
+ jqFilter.val( typeof previousSearch.search !== 'function'
+ ? previousSearch.search
+ : ''
+ );
}
+ } );
- var typeName = 'datetime-' + to;
+ return filter;
+ }, 'f' );
- // Add type detection and sorting specific to this date format - we need to be able to identify
- // date type columns as such, rather than as numbers in extensions. Hence the need for this.
- if (! DataTable.ext.type.order[typeName]) {
- // The renderer will give the value to type detect as the type!
- DataTable.ext.type.detect.unshift(function (d) {
- return d === typeName ? typeName : false;
- });
+ // opts
+ // - type - button configuration
+ // - buttons - number of buttons to show - must be odd
+ DataTable.feature.register( 'paging', function ( settings, opts ) {
+ // Don't show the paging input if the table doesn't have paging enabled
+ if (! settings.oFeatures.bPaginate) {
+ return null;
+ }
- // The renderer gives us Moment, Luxon or Date obects for the sorting, all of which have a
- // `valueOf` which gives milliseconds epoch
- DataTable.ext.type.order[typeName + '-asc'] = function (a, b) {
- var x = a.valueOf();
- var y = b.valueOf();
+ opts = $.extend({
+ buttons: DataTable.ext.pager.numbers_length,
+ type: settings.sPaginationType,
+ boundaryNumbers: true
+ }, opts);
- return x === y
- ? 0
- : x < y
- ? -1
- : 1;
- }
+ // To be removed in 2.1
+ if (opts.numbers) {
+ opts.buttons = opts.numbers;
+ }
+
+ var host = $('<div/>').addClass( settings.oClasses.paging.container + ' paging_' + opts.type );
+ var draw = function () {
+ _pagingDraw(settings, host, opts);
+ };
- DataTable.ext.type.order[typeName + '-desc'] = function (a, b) {
- var x = a.valueOf();
- var y = b.valueOf();
+ settings.aoDrawCallback.push(draw);
- return x === y
- ? 0
- : x > y
- ? -1
- : 1;
- }
+ // Responsive redraw of paging control
+ $(settings.nTable).on('column-sizing.dt.DT', draw);
+
+ return host;
+ }, 'p' );
+
+ function _pagingDraw(settings, host, opts) {
+ if (! settings._bInitComplete) {
+ return;
+ }
+
+ var
+ plugin = DataTable.ext.pager[ opts.type ],
+ aria = settings.oLanguage.oAria.paginate || {},
+ start = settings._iDisplayStart,
+ len = settings._iDisplayLength,
+ visRecords = settings.fnRecordsDisplay(),
+ all = len === -1,
+ page = all ? 0 : Math.ceil( start / len ),
+ pages = all ? 1 : Math.ceil( visRecords / len ),
+ buttons = plugin()
+ .map(function (val) {
+ return val === 'numbers'
+ ? _pagingNumbers(page, pages, opts.buttons, opts.boundaryNumbers)
+ : val;
+ })
+ .flat();
+
+ var buttonEls = [];
+
+ for (var i=0 ; i<buttons.length ; i++) {
+ var button = buttons[i];
+
+ var btnInfo = _pagingButtonInfo(settings, button, page, pages);
+ var btn = _fnRenderer( settings, 'pagingButton' )(
+ settings,
+ button,
+ btnInfo.display,
+ btnInfo.active,
+ btnInfo.disabled
+ );
+
+ // Common attributes
+ $(btn.clicker).attr({
+ 'aria-controls': settings.sTableId,
+ 'aria-disabled': btnInfo.disabled ? 'true' : null,
+ 'aria-current': btnInfo.active ? 'page' : null,
+ 'aria-label': aria[ button ],
+ 'data-dt-idx': button,
+ 'tabIndex': btnInfo.disabled ? -1 : settings.iTabIndex,
+ });
+
+ if (typeof button !== 'number') {
+ $(btn.clicker).addClass(button);
}
-
- return function ( d, type ) {
- // Allow for a default value
- if (d === null || d === undefined) {
- if (def === '--now') {
- // We treat everything as UTC further down, so no changes are
- // made, as such need to get the local date / time as if it were
- // UTC
- var local = new Date();
- d = new Date( Date.UTC(
- local.getFullYear(), local.getMonth(), local.getDate(),
- local.getHours(), local.getMinutes(), local.getSeconds()
- ) );
- }
- else {
- d = '';
- }
+
+ _fnBindAction(
+ btn.clicker, {action: button}, function(e) {
+ e.preventDefault();
+
+ _fnPageChange( settings, e.data.action, true );
}
+ );
+
+ buttonEls.push(btn.display);
+ }
+
+ var wrapped = _fnRenderer(settings, 'pagingContainer')(
+ settings, buttonEls
+ );
+
+ var activeEl = host.find(document.activeElement).data('dt-idx');
+
+ host.empty().append(wrapped);
+
+ if ( activeEl !== undefined ) {
+ host.find( '[data-dt-idx='+activeEl+']' ).trigger('focus');
+ }
+
+ // Responsive - check if the buttons are over two lines based on the
+ // height of the buttons and the container.
+ if (
+ buttonEls.length && // any buttons
+ opts.numbers > 1 && // prevent infinite
+ $(host).height() >= ($(buttonEls[0]).outerHeight() * 2) - 10
+ ) {
+ _pagingDraw(settings, host, $.extend({}, opts, { numbers: opts.numbers - 2 }));
+ }
+ }
+
+ /**
+ * Get properties for a button based on the current paging state of the table
+ *
+ * @param {*} settings DT settings object
+ * @param {*} button The button type in question
+ * @param {*} page Table's current page
+ * @param {*} pages Number of pages
+ * @returns Info object
+ */
+ function _pagingButtonInfo(settings, button, page, pages) {
+ var lang = settings.oLanguage.oPaginate;
+ var o = {
+ display: '',
+ active: false,
+ disabled: false
+ };
- if (type === 'type') {
- // Typing uses the type name for fast matching
- return typeName;
- }
+ switch ( button ) {
+ case 'ellipsis':
+ o.display = '…';
+ o.disabled = true;
+ break;
- if (d === '') {
- return type !== 'sort'
- ? ''
- : __mldObj('0000-01-01 00:00:00', null, locale);
- }
+ case 'first':
+ o.display = lang.sFirst;
- // Shortcut. If `from` and `to` are the same, we are using the renderer to
- // format for ordering, not display - its already in the display format.
- if ( to !== null && from === to && type !== 'sort' && type !== 'type' && ! (d instanceof Date) ) {
- return d;
+ if (page === 0) {
+ o.disabled = true;
}
+ break;
- var dt = __mldObj(d, from, locale);
+ case 'previous':
+ o.display = lang.sPrevious;
- if (dt === null) {
- return d;
+ if ( page === 0 ) {
+ o.disabled = true;
}
+ break;
- if (type === 'sort') {
- return dt;
- }
-
- var formatted = to === null
- ? __mld(dt, 'toDate', 'toJSDate', '')[localeString]()
- : __mld(dt, 'format', 'toFormat', 'toISOString', to);
+ case 'next':
+ o.display = lang.sNext;
- // XSS protection
- return type === 'display' ?
- __htmlEscapeEntities( formatted ) :
- formatted;
- };
- }
- }
+ if ( pages === 0 || page === pages-1 ) {
+ o.disabled = true;
+ }
+ break;
- // Based on locale, determine standard number formatting
- // Fallback for legacy browsers is US English
- var __thousands = ',';
- var __decimal = '.';
+ case 'last':
+ o.display = lang.sLast;
- if (window.Intl !== undefined) {
- try {
- var num = new Intl.NumberFormat().formatToParts(100000.1);
-
- for (var i=0 ; i<num.length ; i++) {
- if (num[i].type === 'group') {
- __thousands = num[i].value;
+ if ( pages === 0 || page === pages-1 ) {
+ o.disabled = true;
}
- else if (num[i].type === 'decimal') {
- __decimal = num[i].value;
+ break;
+
+ default:
+ if ( typeof button === 'number' ) {
+ o.display = settings.fnFormatNumber( button + 1 );
+
+ if (page === button) {
+ o.active = true;
+ }
}
- }
- }
- catch (e) {
- // noop
+ break;
}
+
+ return o;
}
- // Formatted date time detection - use by declaring the formats you are going to use
- DataTable.datetime = function ( format, locale ) {
- var typeName = 'datetime-detect-' + format;
+ /**
+ * Compute what number buttons to show in the paging control
+ *
+ * @param {*} page Current page
+ * @param {*} pages Total number of pages
+ * @param {*} buttons Target number of number buttons
+ * @param {boolean} addFirstLast Indicate if page 1 and end should be included
+ * @returns Buttons to show
+ */
+ function _pagingNumbers ( page, pages, buttons, addFirstLast ) {
+ var
+ numbers = [],
+ half = Math.floor(buttons / 2),
+ before = addFirstLast ? 2 : 1,
+ after = addFirstLast ? 1 : 0;
- if (! locale) {
- locale = 'en';
+ if ( pages <= buttons ) {
+ numbers = _range(0, pages);
}
-
- if (! DataTable.ext.type.order[typeName]) {
- DataTable.ext.type.detect.unshift(function (d) {
- var dt = __mldObj(d, format, locale);
- return d === '' || dt ? typeName : false;
- });
-
- DataTable.ext.type.order[typeName + '-pre'] = function (d) {
- return __mldObj(d, format, locale) || 0;
+ else if (buttons === 1) {
+ // Single button - current page only
+ numbers = [page];
+ }
+ else if (buttons === 3) {
+ // Special logic for just three buttons
+ if (page <= 1) {
+ numbers = [0, 1, 'ellipsis'];
+ }
+ else if (page >= pages - 2) {
+ numbers = _range(pages-2, pages);
+ numbers.unshift('ellipsis');
+ }
+ else {
+ numbers = ['ellipsis', page, 'ellipsis'];
}
}
- }
+ else if ( page <= half ) {
+ numbers = _range(0, buttons-before);
+ numbers.push('ellipsis');
- /**
- * Helpers for `columns.render`.
- *
- * The options defined here can be used with the `columns.render` initialisation
- * option to provide a display renderer. The following functions are defined:
- *
- * * `number` - Will format numeric data (defined by `columns.data`) for
- * display, retaining the original unformatted data for sorting and filtering.
- * It takes 5 parameters:
- * * `string` - Thousands grouping separator
- * * `string` - Decimal point indicator
- * * `integer` - Number of decimal points to show
- * * `string` (optional) - Prefix.
- * * `string` (optional) - Postfix (/suffix).
- * * `text` - Escape HTML to help prevent XSS attacks. It has no optional
- * parameters.
- *
- * @example
- * // Column definition using the number renderer
- * {
- * data: "salary",
- * render: $.fn.dataTable.render.number( '\'', '.', 0, '$' )
- * }
- *
- * @namespace
- */
- DataTable.render = {
- date: __mlHelper('toLocaleDateString'),
- datetime: __mlHelper('toLocaleString'),
- time: __mlHelper('toLocaleTimeString'),
- number: function ( thousands, decimal, precision, prefix, postfix ) {
- // Auto locale detection
- if (thousands === null || thousands === undefined) {
- thousands = __thousands;
+ if (addFirstLast) {
+ numbers.push(pages-1);
}
+ }
+ else if ( page >= pages - 1 - half ) {
+ numbers = _range(pages-(buttons-before), pages);
+ numbers.unshift('ellipsis');
- if (decimal === null || decimal === undefined) {
- decimal = __decimal;
+ if (addFirstLast) {
+ numbers.unshift(0);
}
+ }
+ else {
+ numbers = _range(page-half+before, page+half-after);
+ numbers.push('ellipsis');
+ numbers.unshift('ellipsis');
- return {
- display: function ( d ) {
- if ( typeof d !== 'number' && typeof d !== 'string' ) {
- return d;
- }
+ if (addFirstLast) {
+ numbers.push(pages-1);
+ numbers.unshift(0);
+ }
+ }
- if (d === '' || d === null) {
- return d;
- }
+ return numbers;
+ }
- var negative = d < 0 ? '-' : '';
- var flo = parseFloat( d );
+ var __lengthCounter = 0;
- // If NaN then there isn't much formatting that we can do - just
- // return immediately, escaping any HTML (this was supposed to
- // be a number after all)
- if ( isNaN( flo ) ) {
- return __htmlEscapeEntities( d );
- }
+ // opts
+ // - menu
+ // - text
+ DataTable.feature.register( 'pageLength', function ( settings, opts ) {
+ var features = settings.oFeatures;
- flo = flo.toFixed( precision );
- d = Math.abs( flo );
+ // For compatibility with the legacy `pageLength` top level option
+ if (! features.bPaginate || ! features.bLengthChange) {
+ return null;
+ }
- var intPart = parseInt( d, 10 );
- var floatPart = precision ?
- decimal+(d - intPart).toFixed( precision ).substring( 2 ):
- '';
+ opts = $.extend({
+ menu: settings.aLengthMenu,
+ text: settings.oLanguage.sLengthMenu
+ }, opts);
- // If zero, then can't have a negative prefix
- if (intPart === 0 && parseFloat(floatPart) === 0) {
- negative = '';
- }
+ var
+ classes = settings.oClasses.length,
+ tableId = settings.sTableId,
+ menu = opts.menu,
+ lengths = [],
+ language = [],
+ i;
- return negative + (prefix||'') +
- intPart.toString().replace(
- /\B(?=(\d{3})+(?!\d))/g, thousands
- ) +
- floatPart +
- (postfix||'');
+ // Options can be given in a number of ways
+ if (Array.isArray( menu[0] )) {
+ // Old 1.x style - 2D array
+ lengths = menu[0];
+ language = menu[1];
+ }
+ else {
+ for ( i=0 ; i<menu.length ; i++ ) {
+ // An object with different label and value
+ if ($.isPlainObject(menu[i])) {
+ lengths.push(menu[i].value);
+ language.push(menu[i].label);
}
- };
- },
+ else {
+ // Or just a number to display and use
+ lengths.push(menu[i]);
+ language.push(menu[i]);
+ }
+ }
+ }
- text: function () {
- return {
- display: __htmlEscapeEntities,
- filter: __htmlEscapeEntities
- };
+ // We can put the <select> outside of the label if it is at the start or
+ // end which helps improve accessability (not all screen readers like
+ // implicit for elements).
+ var end = opts.text.match(/_MENU_$/);
+ var start = opts.text.match(/^_MENU_/);
+ var removed = opts.text.replace(/_MENU_/, '');
+ var str = '<label>' + opts.text + '</label>';
+
+ if (start) {
+ str = '_MENU_<label>' + removed + '</label>';
+ }
+ else if (end) {
+ str = '<label>' + removed + '</label>_MENU_';
}
- };
+ // Wrapper element - use a span as a holder for where the select will go
+ var div = $('<div/>')
+ .addClass( classes.container )
+ .append(
+ str.replace( '_MENU_', '<span></span>' )
+ );
- /*
- * This is really a good bit rubbish this method of exposing the internal methods
- * publicly... - To be fixed in 2.0 using methods on the prototype
- */
+ // Save text node content for macro updating
+ var textNodes = [];
+ div.find('label')[0].childNodes.forEach(function (el) {
+ if (el.nodeType === Node.TEXT_NODE) {
+ textNodes.push({
+ el: el,
+ text: el.textContent
+ });
+ }
+ })
+ // Update the label text in case it has an entries value
+ var updateEntries = function (len) {
+ textNodes.forEach(function (node) {
+ node.el.textContent = _fnMacros(settings, node.text, len);
+ });
+ }
- /**
- * Create a wrapper function for exporting an internal functions to an external API.
- * @param {string} fn API function name
- * @returns {function} wrapped function
- * @memberof DataTable#internal
- */
- function _fnExternApiFunc (fn)
- {
- return function() {
- var args = [_fnSettingsFromNode( this[DataTable.ext.iApiIndex] )].concat(
- Array.prototype.slice.call(arguments)
+ // Next, the select itself, along with the options
+ var select = $('<select/>', {
+ 'name': tableId+'_length',
+ 'aria-controls': tableId,
+ 'class': classes.select
+ } );
+
+ for ( i=0 ; i<lengths.length ; i++ ) {
+ select[0][ i ] = new Option(
+ typeof language[i] === 'number' ?
+ settings.fnFormatNumber( language[i] ) :
+ language[i],
+ lengths[i]
);
- return DataTable.ext.internal[fn].apply( this, args );
- };
- }
+ }
+ // add for and id to label and input
+ div.find('label').attr('for', 'dt-length-' + __lengthCounter);
+ select.attr('id', 'dt-length-' + __lengthCounter);
+ __lengthCounter++;
- /**
- * Reference to internal functions for use by plug-in developers. Note that
- * these methods are references to internal functions and are considered to be
- * private. If you use these methods, be aware that they are liable to change
- * between versions.
- * @namespace
- */
- $.extend( DataTable.ext.internal, {
- _fnExternApiFunc: _fnExternApiFunc,
- _fnBuildAjax: _fnBuildAjax,
- _fnAjaxUpdate: _fnAjaxUpdate,
- _fnAjaxParameters: _fnAjaxParameters,
- _fnAjaxUpdateDraw: _fnAjaxUpdateDraw,
- _fnAjaxDataSrc: _fnAjaxDataSrc,
- _fnAddColumn: _fnAddColumn,
- _fnColumnOptions: _fnColumnOptions,
- _fnAdjustColumnSizing: _fnAdjustColumnSizing,
- _fnVisibleToColumnIndex: _fnVisibleToColumnIndex,
- _fnColumnIndexToVisible: _fnColumnIndexToVisible,
- _fnVisbleColumns: _fnVisbleColumns,
- _fnGetColumns: _fnGetColumns,
- _fnColumnTypes: _fnColumnTypes,
- _fnApplyColumnDefs: _fnApplyColumnDefs,
- _fnHungarianMap: _fnHungarianMap,
- _fnCamelToHungarian: _fnCamelToHungarian,
- _fnLanguageCompat: _fnLanguageCompat,
- _fnBrowserDetect: _fnBrowserDetect,
- _fnAddData: _fnAddData,
- _fnAddTr: _fnAddTr,
- _fnNodeToDataIndex: _fnNodeToDataIndex,
- _fnNodeToColumnIndex: _fnNodeToColumnIndex,
- _fnGetCellData: _fnGetCellData,
- _fnSetCellData: _fnSetCellData,
- _fnSplitObjNotation: _fnSplitObjNotation,
- _fnGetObjectDataFn: _fnGetObjectDataFn,
- _fnSetObjectDataFn: _fnSetObjectDataFn,
- _fnGetDataMaster: _fnGetDataMaster,
- _fnClearTable: _fnClearTable,
- _fnDeleteIndex: _fnDeleteIndex,
- _fnInvalidate: _fnInvalidate,
- _fnGetRowElements: _fnGetRowElements,
- _fnCreateTr: _fnCreateTr,
- _fnBuildHead: _fnBuildHead,
- _fnDrawHead: _fnDrawHead,
- _fnDraw: _fnDraw,
- _fnReDraw: _fnReDraw,
- _fnAddOptionsHtml: _fnAddOptionsHtml,
- _fnDetectHeader: _fnDetectHeader,
- _fnGetUniqueThs: _fnGetUniqueThs,
- _fnFeatureHtmlFilter: _fnFeatureHtmlFilter,
- _fnFilterComplete: _fnFilterComplete,
- _fnFilterCustom: _fnFilterCustom,
- _fnFilterColumn: _fnFilterColumn,
- _fnFilter: _fnFilter,
- _fnFilterCreateSearch: _fnFilterCreateSearch,
- _fnEscapeRegex: _fnEscapeRegex,
- _fnFilterData: _fnFilterData,
- _fnFeatureHtmlInfo: _fnFeatureHtmlInfo,
- _fnUpdateInfo: _fnUpdateInfo,
- _fnInfoMacros: _fnInfoMacros,
- _fnInitialise: _fnInitialise,
- _fnInitComplete: _fnInitComplete,
- _fnLengthChange: _fnLengthChange,
- _fnFeatureHtmlLength: _fnFeatureHtmlLength,
- _fnFeatureHtmlPaginate: _fnFeatureHtmlPaginate,
- _fnPageChange: _fnPageChange,
- _fnFeatureHtmlProcessing: _fnFeatureHtmlProcessing,
- _fnProcessingDisplay: _fnProcessingDisplay,
- _fnFeatureHtmlTable: _fnFeatureHtmlTable,
- _fnScrollDraw: _fnScrollDraw,
- _fnApplyToChildren: _fnApplyToChildren,
- _fnCalculateColumnWidths: _fnCalculateColumnWidths,
- _fnThrottle: _fnThrottle,
- _fnConvertToWidth: _fnConvertToWidth,
- _fnGetWidestNode: _fnGetWidestNode,
- _fnGetMaxLenString: _fnGetMaxLenString,
- _fnStringToCss: _fnStringToCss,
- _fnSortFlatten: _fnSortFlatten,
- _fnSort: _fnSort,
- _fnSortAria: _fnSortAria,
- _fnSortListener: _fnSortListener,
- _fnSortAttachListener: _fnSortAttachListener,
- _fnSortingClasses: _fnSortingClasses,
- _fnSortData: _fnSortData,
- _fnSaveState: _fnSaveState,
- _fnLoadState: _fnLoadState,
- _fnImplementState: _fnImplementState,
- _fnSettingsFromNode: _fnSettingsFromNode,
- _fnLog: _fnLog,
- _fnMap: _fnMap,
- _fnBindAction: _fnBindAction,
- _fnCallbackReg: _fnCallbackReg,
- _fnCallbackFire: _fnCallbackFire,
- _fnLengthOverflow: _fnLengthOverflow,
- _fnRenderer: _fnRenderer,
- _fnDataSource: _fnDataSource,
- _fnRowAttributes: _fnRowAttributes,
- _fnExtend: _fnExtend,
- _fnCalculateEnd: function () {} // Used by a lot of plug-ins, but redundant
- // in 1.10, so this dead-end function is
- // added to prevent errors
- } );
+ // Swap in the select list
+ div.find('span').replaceWith(select);
+
+ // Can't use `select` variable as user might provide their own and the
+ // reference is broken by the use of outerHTML
+ $('select', div)
+ .val( settings._iDisplayLength )
+ .on( 'change.DT', function() {
+ _fnLengthChange( settings, $(this).val() );
+ _fnDraw( settings );
+ } );
+
+ // Update node value whenever anything changes the table's length
+ $(settings.nTable).on( 'length.dt.DT', function (e, s, len) {
+ if ( settings === s ) {
+ $('select', div).val( len );
+
+ // Resolve plurals in the text for the new length
+ updateEntries(len);
+ }
+ } );
+
+ updateEntries(settings._iDisplayLength);
+ return div;
+ }, 'l' );
// jQuery access
$.fn.dataTable = DataTable;
@@ -15724,7 +13184,7 @@
/*! DataTables Bootstrap 5 integration
- * 2020 SpryMedia Ltd - datatables.net/license
+ * © SpryMedia Ltd - datatables.net/license
*/
(function( factory ){
@@ -15768,168 +13228,103 @@
// Browser
factory( jQuery, window, document );
}
-}(function( $, window, document, undefined ) {
+}(function( $, window, document ) {
'use strict';
var DataTable = $.fn.dataTable;
/**
- * DataTables integration for Bootstrap 5. This requires Bootstrap 5 and
- * DataTables 1.10 or newer.
+ * DataTables integration for Bootstrap 5.
*
* This file sets the defaults and adds options to DataTables to style its
- * controls using Bootstrap. See http://datatables.net/manual/styling/bootstrap
+ * controls using Bootstrap. See https://datatables.net/manual/styling/bootstrap
* for further information.
*/
/* Set the defaults for DataTables initialisation */
$.extend( true, DataTable.defaults, {
- dom:
- "<'row'<'col-sm-12 col-md-6'l><'col-sm-12 col-md-6'f>>" +
- "<'row dt-row'<'col-sm-12'tr>>" +
- "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
renderer: 'bootstrap'
} );
/* Default class modification */
-$.extend( DataTable.ext.classes, {
- sWrapper: "dataTables_wrapper dt-bootstrap5",
- sFilterInput: "form-control form-control-sm",
- sLengthSelect: "form-select form-select-sm",
- sProcessing: "dataTables_processing card",
- sPageButton: "paginate_button page-item"
+$.extend( true, DataTable.ext.classes, {
+ container: "dt-container dt-bootstrap5",
+ search: {
+ input: "form-control form-control-sm"
+ },
+ length: {
+ select: "form-select form-select-sm"
+ },
+ processing: {
+ container: "dt-processing card"
+ }
} );
/* Bootstrap paging button renderer */
-DataTable.ext.renderer.pageButton.bootstrap = function ( settings, host, idx, buttons, page, pages ) {
- var api = new DataTable.Api( settings );
- var classes = settings.oClasses;
- var lang = settings.oLanguage.oPaginate;
- var aria = settings.oLanguage.oAria.paginate || {};
- var btnDisplay, btnClass;
-
- var attach = function( container, buttons ) {
- var i, ien, node, button;
- var clickHandler = function ( e ) {
- e.preventDefault();
- if ( !$(e.currentTarget).hasClass('disabled') && api.page() != e.data.action ) {
- api.page( e.data.action ).draw( 'page' );
- }
- };
-
- for ( i=0, ien=buttons.length ; i<ien ; i++ ) {
- button = buttons[i];
-
- if ( Array.isArray( button ) ) {
- attach( container, button );
- }
- else {
- btnDisplay = '';
- btnClass = '';
-
- switch ( button ) {
- case 'ellipsis':
- btnDisplay = '…';
- btnClass = 'disabled';
- break;
-
- case 'first':
- btnDisplay = lang.sFirst;
- btnClass = button + (page > 0 ?
- '' : ' disabled');
- break;
-
- case 'previous':
- btnDisplay = lang.sPrevious;
- btnClass = button + (page > 0 ?
- '' : ' disabled');
- break;
+DataTable.ext.renderer.pagingButton.bootstrap = function (settings, buttonType, content, active, disabled) {
+ var btnClasses = ['dt-paging-button', 'page-item'];
- case 'next':
- btnDisplay = lang.sNext;
- btnClass = button + (page < pages-1 ?
- '' : ' disabled');
- break;
-
- case 'last':
- btnDisplay = lang.sLast;
- btnClass = button + (page < pages-1 ?
- '' : ' disabled');
- break;
-
- default:
- btnDisplay = button + 1;
- btnClass = page === button ?
- 'active' : '';
- break;
- }
+ if (active) {
+ btnClasses.push('active');
+ }
- if ( btnDisplay ) {
- var disabled = btnClass.indexOf('disabled') !== -1;
+ if (disabled) {
+ btnClasses.push('disabled')
+ }
- node = $('<li>', {
- 'class': classes.sPageButton+' '+btnClass,
- 'id': idx === 0 && typeof button === 'string' ?
- settings.sTableId +'_'+ button :
- null
- } )
- .append( $('<a>', {
- 'href': disabled ? null : '#',
- 'aria-controls': settings.sTableId,
- 'aria-disabled': disabled ? 'true' : null,
- 'aria-label': aria[ button ],
- 'role': 'link',
- 'aria-current': btnClass === 'active' ? 'page' : null,
- 'data-dt-idx': button,
- 'tabindex': disabled ? -1 : settings.iTabIndex,
- 'class': 'page-link'
- } )
- .html( btnDisplay )
- )
- .appendTo( container );
+ var li = $('<li>').addClass(btnClasses.join(' '));
+ var a = $('<a>', {
+ 'href': disabled ? null : '#',
+ 'class': 'page-link'
+ })
+ .html(content)
+ .appendTo(li);
- settings.oApi._fnBindAction(
- node, {action: button}, clickHandler
- );
- }
- }
- }
+ return {
+ display: li,
+ clicker: a
};
+};
- var hostEl = $(host);
- // IE9 throws an 'unknown error' if document.activeElement is used
- // inside an iframe or frame.
- var activeEl;
-
- try {
- // Because this approach is destroying and recreating the paging
- // elements, focus is lost on the select button which is bad for
- // accessibility. So we want to restore focus once the draw has
- // completed
- activeEl = hostEl.find(document.activeElement).data('dt-idx');
- }
- catch (e) {}
+DataTable.ext.renderer.pagingContainer.bootstrap = function (settings, buttonEls) {
+ return $('<ul/>').addClass('pagination').append(buttonEls);
+};
- var paginationEl = hostEl.children('ul.pagination');
+DataTable.ext.renderer.layout.bootstrap = function ( settings, container, items ) {
+ var row = $( '<div/>', {
+ "class": items.full ?
+ 'row mt-2 justify-content-md-center' :
+ 'row mt-2 justify-content-between'
+ } )
+ .appendTo( container );
- if (paginationEl.length) {
- paginationEl.empty();
- }
- else {
- paginationEl = hostEl.html('<ul/>').children('ul').addClass('pagination');
- }
+ $.each( items, function (key, val) {
+ var klass;
- attach(
- paginationEl,
- buttons
- );
+ // Apply start / end (left / right when ltr) margins
+ if (val.table) {
+ klass = 'col-12';
+ }
+ else if (key === 'start') {
+ klass = 'col-md-auto me-auto';
+ }
+ else if (key === 'end') {
+ klass = 'col-md-auto ms-auto';
+ }
+ else {
+ klass = 'col-md';
+ }
- if ( activeEl !== undefined ) {
- hostEl.find('[data-dt-idx='+activeEl+']').trigger('focus');
- }
+ $( '<div/>', {
+ id: val.id || null,
+ "class": klass + ' ' + (val.className || '')
+ } )
+ .append( val.contents )
+ .appendTo( row );
+ } );
};
diff --git a/src/static/scripts/jdenticon-3.3.0.js b/src/static/scripts/jdenticon-3.3.0.js
@@ -0,0 +1,1507 @@
+/**
+ * Jdenticon 3.3.0
+ * http://jdenticon.com
+ *
+ * Built: 2024-05-10T09:48:41.921Z
+ *
+ * MIT License
+ *
+ * Copyright (c) 2014-2024 Daniel Mester Pirttijärvi
+ *
+ * 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 (umdGlobal, factory) {
+ var jdenticon = factory(umdGlobal);
+
+ // Node.js
+ if (typeof module !== "undefined" && "exports" in module) {
+ module["exports"] = jdenticon;
+ }
+ // RequireJS
+ else if (typeof define === "function" && define["amd"]) {
+ define([], function () { return jdenticon; });
+ }
+ // No module loader
+ else {
+ umdGlobal["jdenticon"] = jdenticon;
+ }
+})(typeof self !== "undefined" ? self : this, function (umdGlobal) {
+'use strict';
+
+/**
+ * Parses a substring of the hash as a number.
+ * @param {number} startPosition
+ * @param {number=} octets
+ */
+function parseHex(hash, startPosition, octets) {
+ return parseInt(hash.substr(startPosition, octets), 16);
+}
+
+function decToHex(v) {
+ v |= 0; // Ensure integer value
+ return v < 0 ? "00" :
+ v < 16 ? "0" + v.toString(16) :
+ v < 256 ? v.toString(16) :
+ "ff";
+}
+
+function hueToRgb(m1, m2, h) {
+ h = h < 0 ? h + 6 : h > 6 ? h - 6 : h;
+ return decToHex(255 * (
+ h < 1 ? m1 + (m2 - m1) * h :
+ h < 3 ? m2 :
+ h < 4 ? m1 + (m2 - m1) * (4 - h) :
+ m1));
+}
+
+/**
+ * @param {string} color Color value to parse. Currently hexadecimal strings on the format #rgb[a] and #rrggbb[aa] are supported.
+ * @returns {string}
+ */
+function parseColor(color) {
+ if (/^#[0-9a-f]{3,8}$/i.test(color)) {
+ var result;
+ var colorLength = color.length;
+
+ if (colorLength < 6) {
+ var r = color[1],
+ g = color[2],
+ b = color[3],
+ a = color[4] || "";
+ result = "#" + r + r + g + g + b + b + a + a;
+ }
+ if (colorLength == 7 || colorLength > 8) {
+ result = color;
+ }
+
+ return result;
+ }
+}
+
+/**
+ * Converts a hexadecimal color to a CSS3 compatible color.
+ * @param {string} hexColor Color on the format "#RRGGBB" or "#RRGGBBAA"
+ * @returns {string}
+ */
+function toCss3Color(hexColor) {
+ var a = parseHex(hexColor, 7, 2);
+ var result;
+
+ if (isNaN(a)) {
+ result = hexColor;
+ } else {
+ var r = parseHex(hexColor, 1, 2),
+ g = parseHex(hexColor, 3, 2),
+ b = parseHex(hexColor, 5, 2);
+ result = "rgba(" + r + "," + g + "," + b + "," + (a / 255).toFixed(2) + ")";
+ }
+
+ return result;
+}
+
+/**
+ * Converts an HSL color to a hexadecimal RGB color.
+ * @param {number} hue Hue in range [0, 1]
+ * @param {number} saturation Saturation in range [0, 1]
+ * @param {number} lightness Lightness in range [0, 1]
+ * @returns {string}
+ */
+function hsl(hue, saturation, lightness) {
+ // Based on http://www.w3.org/TR/2011/REC-css3-color-20110607/#hsl-color
+ var result;
+
+ if (saturation == 0) {
+ var partialHex = decToHex(lightness * 255);
+ result = partialHex + partialHex + partialHex;
+ }
+ else {
+ var m2 = lightness <= 0.5 ? lightness * (saturation + 1) : lightness + saturation - lightness * saturation,
+ m1 = lightness * 2 - m2;
+ result =
+ hueToRgb(m1, m2, hue * 6 + 2) +
+ hueToRgb(m1, m2, hue * 6) +
+ hueToRgb(m1, m2, hue * 6 - 2);
+ }
+
+ return "#" + result;
+}
+
+/**
+ * Converts an HSL color to a hexadecimal RGB color. This function will correct the lightness for the "dark" hues
+ * @param {number} hue Hue in range [0, 1]
+ * @param {number} saturation Saturation in range [0, 1]
+ * @param {number} lightness Lightness in range [0, 1]
+ * @returns {string}
+ */
+function correctedHsl(hue, saturation, lightness) {
+ // The corrector specifies the perceived middle lightness for each hue
+ var correctors = [ 0.55, 0.5, 0.5, 0.46, 0.6, 0.55, 0.55 ],
+ corrector = correctors[(hue * 6 + 0.5) | 0];
+
+ // Adjust the input lightness relative to the corrector
+ lightness = lightness < 0.5 ? lightness * corrector * 2 : corrector + (lightness - 0.5) * (1 - corrector) * 2;
+
+ return hsl(hue, saturation, lightness);
+}
+
+/* global umdGlobal */
+
+// In the future we can replace `GLOBAL` with `globalThis`, but for now use the old school global detection for
+// backward compatibility.
+var GLOBAL = umdGlobal;
+
+/**
+ * @typedef {Object} ParsedConfiguration
+ * @property {number} colorSaturation
+ * @property {number} grayscaleSaturation
+ * @property {string} backColor
+ * @property {number} iconPadding
+ * @property {function(number):number} hue
+ * @property {function(number):number} colorLightness
+ * @property {function(number):number} grayscaleLightness
+ */
+
+var CONFIG_PROPERTIES = {
+ G/*GLOBAL*/: "jdenticon_config",
+ n/*MODULE*/: "config",
+};
+
+var rootConfigurationHolder = {};
+
+/**
+ * Defines the deprecated `config` property on the root Jdenticon object without printing a warning in the console
+ * when it is being used.
+ * @param {!Object} rootObject
+ */
+function defineConfigProperty(rootObject) {
+ rootConfigurationHolder = rootObject;
+}
+
+/**
+ * Sets a new icon style configuration. The new configuration is not merged with the previous one. *
+ * @param {Object} newConfiguration - New configuration object.
+ */
+function configure(newConfiguration) {
+ if (arguments.length) {
+ rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/] = newConfiguration;
+ }
+ return rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/];
+}
+
+/**
+ * Gets the normalized current Jdenticon color configuration. Missing fields have default values.
+ * @param {Object|number|undefined} paddingOrLocalConfig - Configuration passed to the called API method. A
+ * local configuration overrides the global configuration in it entirety. This parameter can for backward
+ * compatibility also contain a padding value. A padding value only overrides the global padding, not the
+ * entire global configuration.
+ * @param {number} defaultPadding - Padding used if no padding is specified in neither the configuration nor
+ * explicitly to the API method.
+ * @returns {ParsedConfiguration}
+ */
+function getConfiguration(paddingOrLocalConfig, defaultPadding) {
+ var configObject =
+ typeof paddingOrLocalConfig == "object" && paddingOrLocalConfig ||
+ rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/] ||
+ GLOBAL[CONFIG_PROPERTIES.G/*GLOBAL*/] ||
+ { },
+
+ lightnessConfig = configObject["lightness"] || { },
+
+ // In versions < 2.1.0 there was no grayscale saturation -
+ // saturation was the color saturation.
+ saturation = configObject["saturation"] || { },
+ colorSaturation = "color" in saturation ? saturation["color"] : saturation,
+ grayscaleSaturation = saturation["grayscale"],
+
+ backColor = configObject["backColor"],
+ padding = configObject["padding"];
+
+ /**
+ * Creates a lightness range.
+ */
+ function lightness(configName, defaultRange) {
+ var range = lightnessConfig[configName];
+
+ // Check if the lightness range is an array-like object. This way we ensure the
+ // array contain two values at the same time.
+ if (!(range && range.length > 1)) {
+ range = defaultRange;
+ }
+
+ /**
+ * Gets a lightness relative the specified value in the specified lightness range.
+ */
+ return function (value) {
+ value = range[0] + value * (range[1] - range[0]);
+ return value < 0 ? 0 : value > 1 ? 1 : value;
+ };
+ }
+
+ /**
+ * Gets a hue allowed by the configured hue restriction,
+ * provided the originally computed hue.
+ */
+ function hueFunction(originalHue) {
+ var hueConfig = configObject["hues"];
+ var hue;
+
+ // Check if 'hues' is an array-like object. This way we also ensure that
+ // the array is not empty, which would mean no hue restriction.
+ if (hueConfig && hueConfig.length > 0) {
+ // originalHue is in the range [0, 1]
+ // Multiply with 0.999 to change the range to [0, 1) and then truncate the index.
+ hue = hueConfig[0 | (0.999 * originalHue * hueConfig.length)];
+ }
+
+ return typeof hue == "number" ?
+
+ // A hue was specified. We need to convert the hue from
+ // degrees on any turn - e.g. 746° is a perfectly valid hue -
+ // to turns in the range [0, 1).
+ ((((hue / 360) % 1) + 1) % 1) :
+
+ // No hue configured => use original hue
+ originalHue;
+ }
+
+ return {
+ X/*hue*/: hueFunction,
+ p/*colorSaturation*/: typeof colorSaturation == "number" ? colorSaturation : 0.5,
+ H/*grayscaleSaturation*/: typeof grayscaleSaturation == "number" ? grayscaleSaturation : 0,
+ q/*colorLightness*/: lightness("color", [0.4, 0.8]),
+ I/*grayscaleLightness*/: lightness("grayscale", [0.3, 0.9]),
+ J/*backColor*/: parseColor(backColor),
+ Y/*iconPadding*/:
+ typeof paddingOrLocalConfig == "number" ? paddingOrLocalConfig :
+ typeof padding == "number" ? padding :
+ defaultPadding
+ }
+}
+
+var ICON_TYPE_SVG = 1;
+
+var ICON_TYPE_CANVAS = 2;
+
+var ATTRIBUTES = {
+ t/*HASH*/: "data-jdenticon-hash",
+ o/*VALUE*/: "data-jdenticon-value"
+};
+
+var IS_RENDERED_PROPERTY = "jdenticonRendered";
+
+var ICON_SELECTOR = "[" + ATTRIBUTES.t/*HASH*/ +"],[" + ATTRIBUTES.o/*VALUE*/ +"]";
+
+var documentQuerySelectorAll = /** @type {!Function} */ (
+ typeof document !== "undefined" && document.querySelectorAll.bind(document));
+
+function getIdenticonType(el) {
+ if (el) {
+ var tagName = el["tagName"];
+
+ if (/^svg$/i.test(tagName)) {
+ return ICON_TYPE_SVG;
+ }
+
+ if (/^canvas$/i.test(tagName) && "getContext" in el) {
+ return ICON_TYPE_CANVAS;
+ }
+ }
+}
+
+function whenDocumentIsReady(/** @type {Function} */ callback) {
+ function loadedHandler() {
+ document.removeEventListener("DOMContentLoaded", loadedHandler);
+ window.removeEventListener("load", loadedHandler);
+ setTimeout(callback, 0); // Give scripts a chance to run
+ }
+
+ if (typeof document !== "undefined" &&
+ typeof window !== "undefined" &&
+ typeof setTimeout !== "undefined"
+ ) {
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", loadedHandler);
+ window.addEventListener("load", loadedHandler);
+ } else {
+ // Document already loaded. The load events above likely won't be raised
+ setTimeout(callback, 0);
+ }
+ }
+}
+
+function observer(updateCallback) {
+ if (typeof MutationObserver != "undefined") {
+ var mutationObserver = new MutationObserver(function onmutation(mutations) {
+ for (var mutationIndex = 0; mutationIndex < mutations.length; mutationIndex++) {
+ var mutation = mutations[mutationIndex];
+ var addedNodes = mutation.addedNodes;
+
+ for (var addedNodeIndex = 0; addedNodes && addedNodeIndex < addedNodes.length; addedNodeIndex++) {
+ var addedNode = addedNodes[addedNodeIndex];
+
+ // Skip other types of nodes than element nodes, since they might not support
+ // the querySelectorAll method => runtime error.
+ if (addedNode.nodeType == 1) {
+ if (getIdenticonType(addedNode)) {
+ updateCallback(addedNode);
+ }
+ else {
+ var icons = /** @type {Element} */(addedNode).querySelectorAll(ICON_SELECTOR);
+ for (var iconIndex = 0; iconIndex < icons.length; iconIndex++) {
+ updateCallback(icons[iconIndex]);
+ }
+ }
+ }
+ }
+
+ if (mutation.type == "attributes" && getIdenticonType(mutation.target)) {
+ updateCallback(mutation.target);
+ }
+ }
+ });
+
+ mutationObserver.observe(document.body, {
+ "childList": true,
+ "attributes": true,
+ "attributeFilter": [ATTRIBUTES.o/*VALUE*/, ATTRIBUTES.t/*HASH*/, "width", "height"],
+ "subtree": true,
+ });
+ }
+}
+
+/**
+ * Represents a point.
+ */
+function Point(x, y) {
+ this.x = x;
+ this.y = y;
+}
+
+/**
+ * Translates and rotates a point before being passed on to the canvas context. This was previously done by the canvas context itself,
+ * but this caused a rendering issue in Chrome on sizes > 256 where the rotation transformation of inverted paths was not done properly.
+ */
+function Transform(x, y, size, rotation) {
+ this.u/*_x*/ = x;
+ this.v/*_y*/ = y;
+ this.K/*_size*/ = size;
+ this.Z/*_rotation*/ = rotation;
+}
+
+/**
+ * Transforms the specified point based on the translation and rotation specification for this Transform.
+ * @param {number} x x-coordinate
+ * @param {number} y y-coordinate
+ * @param {number=} w The width of the transformed rectangle. If greater than 0, this will ensure the returned point is of the upper left corner of the transformed rectangle.
+ * @param {number=} h The height of the transformed rectangle. If greater than 0, this will ensure the returned point is of the upper left corner of the transformed rectangle.
+ */
+Transform.prototype.L/*transformIconPoint*/ = function transformIconPoint (x, y, w, h) {
+ var right = this.u/*_x*/ + this.K/*_size*/,
+ bottom = this.v/*_y*/ + this.K/*_size*/,
+ rotation = this.Z/*_rotation*/;
+ return rotation === 1 ? new Point(right - y - (h || 0), this.v/*_y*/ + x) :
+ rotation === 2 ? new Point(right - x - (w || 0), bottom - y - (h || 0)) :
+ rotation === 3 ? new Point(this.u/*_x*/ + y, bottom - x - (w || 0)) :
+ new Point(this.u/*_x*/ + x, this.v/*_y*/ + y);
+};
+
+var NO_TRANSFORM = new Transform(0, 0, 0, 0);
+
+
+
+/**
+ * Provides helper functions for rendering common basic shapes.
+ */
+function Graphics(renderer) {
+ /**
+ * @type {Renderer}
+ * @private
+ */
+ this.M/*_renderer*/ = renderer;
+
+ /**
+ * @type {Transform}
+ */
+ this.A/*currentTransform*/ = NO_TRANSFORM;
+}
+var Graphics__prototype = Graphics.prototype;
+
+/**
+ * Adds a polygon to the underlying renderer.
+ * @param {Array<number>} points The points of the polygon clockwise on the format [ x0, y0, x1, y1, ..., xn, yn ]
+ * @param {boolean=} invert Specifies if the polygon will be inverted.
+ */
+Graphics__prototype.g/*addPolygon*/ = function addPolygon (points, invert) {
+ var this$1 = this;
+
+ var di = invert ? -2 : 2,
+ transformedPoints = [];
+
+ for (var i = invert ? points.length - 2 : 0; i < points.length && i >= 0; i += di) {
+ transformedPoints.push(this$1.A/*currentTransform*/.L/*transformIconPoint*/(points[i], points[i + 1]));
+ }
+
+ this.M/*_renderer*/.g/*addPolygon*/(transformedPoints);
+};
+
+/**
+ * Adds a polygon to the underlying renderer.
+ * Source: http://stackoverflow.com/a/2173084
+ * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the entire ellipse.
+ * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the entire ellipse.
+ * @param {number} size The size of the ellipse.
+ * @param {boolean=} invert Specifies if the ellipse will be inverted.
+ */
+Graphics__prototype.h/*addCircle*/ = function addCircle (x, y, size, invert) {
+ var p = this.A/*currentTransform*/.L/*transformIconPoint*/(x, y, size, size);
+ this.M/*_renderer*/.h/*addCircle*/(p, size, invert);
+};
+
+/**
+ * Adds a rectangle to the underlying renderer.
+ * @param {number} x The x-coordinate of the upper left corner of the rectangle.
+ * @param {number} y The y-coordinate of the upper left corner of the rectangle.
+ * @param {number} w The width of the rectangle.
+ * @param {number} h The height of the rectangle.
+ * @param {boolean=} invert Specifies if the rectangle will be inverted.
+ */
+Graphics__prototype.i/*addRectangle*/ = function addRectangle (x, y, w, h, invert) {
+ this.g/*addPolygon*/([
+ x, y,
+ x + w, y,
+ x + w, y + h,
+ x, y + h
+ ], invert);
+};
+
+/**
+ * Adds a right triangle to the underlying renderer.
+ * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the triangle.
+ * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the triangle.
+ * @param {number} w The width of the triangle.
+ * @param {number} h The height of the triangle.
+ * @param {number} r The rotation of the triangle (clockwise). 0 = right corner of the triangle in the lower left corner of the bounding rectangle.
+ * @param {boolean=} invert Specifies if the triangle will be inverted.
+ */
+Graphics__prototype.j/*addTriangle*/ = function addTriangle (x, y, w, h, r, invert) {
+ var points = [
+ x + w, y,
+ x + w, y + h,
+ x, y + h,
+ x, y
+ ];
+ points.splice(((r || 0) % 4) * 2, 2);
+ this.g/*addPolygon*/(points, invert);
+};
+
+/**
+ * Adds a rhombus to the underlying renderer.
+ * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the rhombus.
+ * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the rhombus.
+ * @param {number} w The width of the rhombus.
+ * @param {number} h The height of the rhombus.
+ * @param {boolean=} invert Specifies if the rhombus will be inverted.
+ */
+Graphics__prototype.N/*addRhombus*/ = function addRhombus (x, y, w, h, invert) {
+ this.g/*addPolygon*/([
+ x + w / 2, y,
+ x + w, y + h / 2,
+ x + w / 2, y + h,
+ x, y + h / 2
+ ], invert);
+};
+
+/**
+ * @param {number} index
+ * @param {Graphics} g
+ * @param {number} cell
+ * @param {number} positionIndex
+ */
+function centerShape(index, g, cell, positionIndex) {
+ index = index % 14;
+
+ var k, m, w, h, inner, outer;
+
+ !index ? (
+ k = cell * 0.42,
+ g.g/*addPolygon*/([
+ 0, 0,
+ cell, 0,
+ cell, cell - k * 2,
+ cell - k, cell,
+ 0, cell
+ ])) :
+
+ index == 1 ? (
+ w = 0 | (cell * 0.5),
+ h = 0 | (cell * 0.8),
+
+ g.j/*addTriangle*/(cell - w, 0, w, h, 2)) :
+
+ index == 2 ? (
+ w = 0 | (cell / 3),
+ g.i/*addRectangle*/(w, w, cell - w, cell - w)) :
+
+ index == 3 ? (
+ inner = cell * 0.1,
+ // Use fixed outer border widths in small icons to ensure the border is drawn
+ outer =
+ cell < 6 ? 1 :
+ cell < 8 ? 2 :
+ (0 | (cell * 0.25)),
+
+ inner =
+ inner > 1 ? (0 | inner) : // large icon => truncate decimals
+ inner > 0.5 ? 1 : // medium size icon => fixed width
+ inner, // small icon => anti-aliased border
+
+ g.i/*addRectangle*/(outer, outer, cell - inner - outer, cell - inner - outer)) :
+
+ index == 4 ? (
+ m = 0 | (cell * 0.15),
+ w = 0 | (cell * 0.5),
+ g.h/*addCircle*/(cell - w - m, cell - w - m, w)) :
+
+ index == 5 ? (
+ inner = cell * 0.1,
+ outer = inner * 4,
+
+ // Align edge to nearest pixel in large icons
+ outer > 3 && (outer = 0 | outer),
+
+ g.i/*addRectangle*/(0, 0, cell, cell),
+ g.g/*addPolygon*/([
+ outer, outer,
+ cell - inner, outer,
+ outer + (cell - outer - inner) / 2, cell - inner
+ ], true)) :
+
+ index == 6 ?
+ g.g/*addPolygon*/([
+ 0, 0,
+ cell, 0,
+ cell, cell * 0.7,
+ cell * 0.4, cell * 0.4,
+ cell * 0.7, cell,
+ 0, cell
+ ]) :
+
+ index == 7 ?
+ g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 3) :
+
+ index == 8 ? (
+ g.i/*addRectangle*/(0, 0, cell, cell / 2),
+ g.i/*addRectangle*/(0, cell / 2, cell / 2, cell / 2),
+ g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 1)) :
+
+ index == 9 ? (
+ inner = cell * 0.14,
+ // Use fixed outer border widths in small icons to ensure the border is drawn
+ outer =
+ cell < 4 ? 1 :
+ cell < 6 ? 2 :
+ (0 | (cell * 0.35)),
+
+ inner =
+ cell < 8 ? inner : // small icon => anti-aliased border
+ (0 | inner), // large icon => truncate decimals
+
+ g.i/*addRectangle*/(0, 0, cell, cell),
+ g.i/*addRectangle*/(outer, outer, cell - outer - inner, cell - outer - inner, true)) :
+
+ index == 10 ? (
+ inner = cell * 0.12,
+ outer = inner * 3,
+
+ g.i/*addRectangle*/(0, 0, cell, cell),
+ g.h/*addCircle*/(outer, outer, cell - inner - outer, true)) :
+
+ index == 11 ?
+ g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 3) :
+
+ index == 12 ? (
+ m = cell * 0.25,
+ g.i/*addRectangle*/(0, 0, cell, cell),
+ g.N/*addRhombus*/(m, m, cell - m, cell - m, true)) :
+
+ // 13
+ (
+ !positionIndex && (
+ m = cell * 0.4, w = cell * 1.2,
+ g.h/*addCircle*/(m, m, w)
+ )
+ );
+}
+
+/**
+ * @param {number} index
+ * @param {Graphics} g
+ * @param {number} cell
+ */
+function outerShape(index, g, cell) {
+ index = index % 4;
+
+ var m;
+
+ !index ?
+ g.j/*addTriangle*/(0, 0, cell, cell, 0) :
+
+ index == 1 ?
+ g.j/*addTriangle*/(0, cell / 2, cell, cell / 2, 0) :
+
+ index == 2 ?
+ g.N/*addRhombus*/(0, 0, cell, cell) :
+
+ // 3
+ (
+ m = cell / 6,
+ g.h/*addCircle*/(m, m, cell - 2 * m)
+ );
+}
+
+/**
+ * Gets a set of identicon color candidates for a specified hue and config.
+ * @param {number} hue
+ * @param {ParsedConfiguration} config
+ */
+function colorTheme(hue, config) {
+ hue = config.X/*hue*/(hue);
+ return [
+ // Dark gray
+ correctedHsl(hue, config.H/*grayscaleSaturation*/, config.I/*grayscaleLightness*/(0)),
+ // Mid color
+ correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(0.5)),
+ // Light gray
+ correctedHsl(hue, config.H/*grayscaleSaturation*/, config.I/*grayscaleLightness*/(1)),
+ // Light color
+ correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(1)),
+ // Dark color
+ correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(0))
+ ];
+}
+
+/**
+ * Draws an identicon to a specified renderer.
+ * @param {Renderer} renderer
+ * @param {string} hash
+ * @param {Object|number=} config
+ */
+function iconGenerator(renderer, hash, config) {
+ var parsedConfig = getConfiguration(config, 0.08);
+
+ // Set background color
+ if (parsedConfig.J/*backColor*/) {
+ renderer.m/*setBackground*/(parsedConfig.J/*backColor*/);
+ }
+
+ // Calculate padding and round to nearest integer
+ var size = renderer.k/*iconSize*/;
+ var padding = (0.5 + size * parsedConfig.Y/*iconPadding*/) | 0;
+ size -= padding * 2;
+
+ var graphics = new Graphics(renderer);
+
+ // Calculate cell size and ensure it is an integer
+ var cell = 0 | (size / 4);
+
+ // Since the cell size is integer based, the actual icon will be slightly smaller than specified => center icon
+ var x = 0 | (padding + size / 2 - cell * 2);
+ var y = 0 | (padding + size / 2 - cell * 2);
+
+ function renderShape(colorIndex, shapes, index, rotationIndex, positions) {
+ var shapeIndex = parseHex(hash, index, 1);
+ var r = rotationIndex ? parseHex(hash, rotationIndex, 1) : 0;
+
+ renderer.O/*beginShape*/(availableColors[selectedColorIndexes[colorIndex]]);
+
+ for (var i = 0; i < positions.length; i++) {
+ graphics.A/*currentTransform*/ = new Transform(x + positions[i][0] * cell, y + positions[i][1] * cell, cell, r++ % 4);
+ shapes(shapeIndex, graphics, cell, i);
+ }
+
+ renderer.P/*endShape*/();
+ }
+
+ // AVAILABLE COLORS
+ var hue = parseHex(hash, -7) / 0xfffffff,
+
+ // Available colors for this icon
+ availableColors = colorTheme(hue, parsedConfig),
+
+ // The index of the selected colors
+ selectedColorIndexes = [];
+
+ var index;
+
+ function isDuplicate(values) {
+ if (values.indexOf(index) >= 0) {
+ for (var i = 0; i < values.length; i++) {
+ if (selectedColorIndexes.indexOf(values[i]) >= 0) {
+ return true;
+ }
+ }
+ }
+ }
+
+ for (var i = 0; i < 3; i++) {
+ index = parseHex(hash, 8 + i, 1) % availableColors.length;
+ if (isDuplicate([0, 4]) || // Disallow dark gray and dark color combo
+ isDuplicate([2, 3])) { // Disallow light gray and light color combo
+ index = 1;
+ }
+ selectedColorIndexes.push(index);
+ }
+
+ // ACTUAL RENDERING
+ // Sides
+ renderShape(0, outerShape, 2, 3, [[1, 0], [2, 0], [2, 3], [1, 3], [0, 1], [3, 1], [3, 2], [0, 2]]);
+ // Corners
+ renderShape(1, outerShape, 4, 5, [[0, 0], [3, 0], [3, 3], [0, 3]]);
+ // Center
+ renderShape(2, centerShape, 1, null, [[1, 1], [2, 1], [2, 2], [1, 2]]);
+
+ renderer.finish();
+}
+
+/**
+ * Computes a SHA1 hash for any value and returns it as a hexadecimal string.
+ *
+ * This function is optimized for minimal code size and rather short messages.
+ *
+ * @param {string} message
+ */
+function sha1(message) {
+ var HASH_SIZE_HALF_BYTES = 40;
+ var BLOCK_SIZE_WORDS = 16;
+
+ // Variables
+ // `var` is used to be able to minimize the number of `var` keywords.
+ var i = 0,
+ f = 0,
+
+ // Use `encodeURI` to UTF8 encode the message without any additional libraries
+ // We could use `unescape` + `encodeURI` to minimize the code, but that would be slightly risky
+ // since `unescape` is deprecated.
+ urlEncodedMessage = encodeURI(message) + "%80", // trailing '1' bit padding
+
+ // This can be changed to a preallocated Uint32Array array for greater performance and larger code size
+ data = [],
+ dataSize,
+
+ hashBuffer = [],
+
+ a = 0x67452301,
+ b = 0xefcdab89,
+ c = ~a,
+ d = ~b,
+ e = 0xc3d2e1f0,
+ hash = [a, b, c, d, e],
+
+ blockStartIndex = 0,
+ hexHash = "";
+
+ /**
+ * Rotates the value a specified number of bits to the left.
+ * @param {number} value Value to rotate
+ * @param {number} shift Bit count to shift.
+ */
+ function rotl(value, shift) {
+ return (value << shift) | (value >>> (32 - shift));
+ }
+
+ // Message data
+ for ( ; i < urlEncodedMessage.length; f++) {
+ data[f >> 2] = data[f >> 2] |
+ (
+ (
+ urlEncodedMessage[i] == "%"
+ // Percent encoded byte
+ ? parseInt(urlEncodedMessage.substring(i + 1, i += 3), 16)
+ // Unencoded byte
+ : urlEncodedMessage.charCodeAt(i++)
+ )
+
+ // Read bytes in reverse order (big endian words)
+ << ((3 - (f & 3)) * 8)
+ );
+ }
+
+ // f is now the length of the utf8 encoded message
+ // 7 = 8 bytes (64 bit) for message size, -1 to round down
+ // >> 6 = integer division with block size
+ dataSize = (((f + 7) >> 6) + 1) * BLOCK_SIZE_WORDS;
+
+ // Message size in bits.
+ // SHA1 uses a 64 bit integer to represent the size, but since we only support short messages only the least
+ // significant 32 bits are set. -8 is for the '1' bit padding byte.
+ data[dataSize - 1] = f * 8 - 8;
+
+ // Compute hash
+ for ( ; blockStartIndex < dataSize; blockStartIndex += BLOCK_SIZE_WORDS) {
+ for (i = 0; i < 80; i++) {
+ f = rotl(a, 5) + e + (
+ // Ch
+ i < 20 ? ((b & c) ^ ((~b) & d)) + 0x5a827999 :
+
+ // Parity
+ i < 40 ? (b ^ c ^ d) + 0x6ed9eba1 :
+
+ // Maj
+ i < 60 ? ((b & c) ^ (b & d) ^ (c & d)) + 0x8f1bbcdc :
+
+ // Parity
+ (b ^ c ^ d) + 0xca62c1d6
+ ) + (
+ hashBuffer[i] = i < BLOCK_SIZE_WORDS
+ // Bitwise OR is used to coerse `undefined` to 0
+ ? (data[blockStartIndex + i] | 0)
+ : rotl(hashBuffer[i - 3] ^ hashBuffer[i - 8] ^ hashBuffer[i - 14] ^ hashBuffer[i - 16], 1)
+ );
+
+ e = d;
+ d = c;
+ c = rotl(b, 30);
+ b = a;
+ a = f;
+ }
+
+ hash[0] = a = ((hash[0] + a) | 0);
+ hash[1] = b = ((hash[1] + b) | 0);
+ hash[2] = c = ((hash[2] + c) | 0);
+ hash[3] = d = ((hash[3] + d) | 0);
+ hash[4] = e = ((hash[4] + e) | 0);
+ }
+
+ // Format hex hash
+ for (i = 0; i < HASH_SIZE_HALF_BYTES; i++) {
+ hexHash += (
+ (
+ // Get word (2^3 half-bytes per word)
+ hash[i >> 3] >>>
+
+ // Append half-bytes in reverse order
+ ((7 - (i & 7)) * 4)
+ )
+ // Clamp to half-byte
+ & 0xf
+ ).toString(16);
+ }
+
+ return hexHash;
+}
+
+/**
+ * Inputs a value that might be a valid hash string for Jdenticon and returns it
+ * if it is determined valid, otherwise a falsy value is returned.
+ */
+function isValidHash(hashCandidate) {
+ return /^[0-9a-f]{11,}$/i.test(hashCandidate) && hashCandidate;
+}
+
+/**
+ * Computes a hash for the specified value. Currently SHA1 is used. This function
+ * always returns a valid hash.
+ */
+function computeHash(value) {
+ return sha1(value == null ? "" : "" + value);
+}
+
+
+
+/**
+ * Renderer redirecting drawing commands to a canvas context.
+ * @implements {Renderer}
+ */
+function CanvasRenderer(ctx, iconSize) {
+ var canvas = ctx.canvas;
+ var width = canvas.width;
+ var height = canvas.height;
+
+ ctx.save();
+
+ if (!iconSize) {
+ iconSize = Math.min(width, height);
+
+ ctx.translate(
+ ((width - iconSize) / 2) | 0,
+ ((height - iconSize) / 2) | 0);
+ }
+
+ /**
+ * @private
+ */
+ this.l/*_ctx*/ = ctx;
+ this.k/*iconSize*/ = iconSize;
+
+ ctx.clearRect(0, 0, iconSize, iconSize);
+}
+var CanvasRenderer__prototype = CanvasRenderer.prototype;
+
+/**
+ * Fills the background with the specified color.
+ * @param {string} fillColor Fill color on the format #rrggbb[aa].
+ */
+CanvasRenderer__prototype.m/*setBackground*/ = function setBackground (fillColor) {
+ var ctx = this.l/*_ctx*/;
+ var iconSize = this.k/*iconSize*/;
+
+ ctx.fillStyle = toCss3Color(fillColor);
+ ctx.fillRect(0, 0, iconSize, iconSize);
+};
+
+/**
+ * Marks the beginning of a new shape of the specified color. Should be ended with a call to endShape.
+ * @param {string} fillColor Fill color on format #rrggbb[aa].
+ */
+CanvasRenderer__prototype.O/*beginShape*/ = function beginShape (fillColor) {
+ var ctx = this.l/*_ctx*/;
+ ctx.fillStyle = toCss3Color(fillColor);
+ ctx.beginPath();
+};
+
+/**
+ * Marks the end of the currently drawn shape. This causes the queued paths to be rendered on the canvas.
+ */
+CanvasRenderer__prototype.P/*endShape*/ = function endShape () {
+ this.l/*_ctx*/.fill();
+};
+
+/**
+ * Adds a polygon to the rendering queue.
+ * @param points An array of Point objects.
+ */
+CanvasRenderer__prototype.g/*addPolygon*/ = function addPolygon (points) {
+ var ctx = this.l/*_ctx*/;
+ ctx.moveTo(points[0].x, points[0].y);
+ for (var i = 1; i < points.length; i++) {
+ ctx.lineTo(points[i].x, points[i].y);
+ }
+ ctx.closePath();
+};
+
+/**
+ * Adds a circle to the rendering queue.
+ * @param {Point} point The upper left corner of the circle bounding box.
+ * @param {number} diameter The diameter of the circle.
+ * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path).
+ */
+CanvasRenderer__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) {
+ var ctx = this.l/*_ctx*/,
+ radius = diameter / 2;
+ ctx.moveTo(point.x + radius, point.y + radius);
+ ctx.arc(point.x + radius, point.y + radius, radius, 0, Math.PI * 2, counterClockwise);
+ ctx.closePath();
+};
+
+/**
+ * Called when the icon has been completely drawn.
+ */
+CanvasRenderer__prototype.finish = function finish () {
+ this.l/*_ctx*/.restore();
+};
+
+/**
+ * Draws an identicon to a context.
+ * @param {CanvasRenderingContext2D} ctx - Canvas context on which the icon will be drawn at location (0, 0).
+ * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon.
+ * @param {number} size - Icon size in pixels.
+ * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any
+ * global configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be
+ * specified in place of a configuration object.
+ */
+function drawIcon(ctx, hashOrValue, size, config) {
+ if (!ctx) {
+ throw new Error("No canvas specified.");
+ }
+
+ iconGenerator(new CanvasRenderer(ctx, size),
+ isValidHash(hashOrValue) || computeHash(hashOrValue),
+ config);
+
+ var canvas = ctx.canvas;
+ if (canvas) {
+ canvas[IS_RENDERED_PROPERTY] = true;
+ }
+}
+
+/**
+ * Prepares a measure to be used as a measure in an SVG path, by
+ * rounding the measure to a single decimal. This reduces the file
+ * size of the generated SVG with more than 50% in some cases.
+ */
+function svgValue(value) {
+ return ((value * 10 + 0.5) | 0) / 10;
+}
+
+/**
+ * Represents an SVG path element.
+ */
+function SvgPath() {
+ /**
+ * This property holds the data string (path.d) of the SVG path.
+ * @type {string}
+ */
+ this.B/*dataString*/ = "";
+}
+var SvgPath__prototype = SvgPath.prototype;
+
+/**
+ * Adds a polygon with the current fill color to the SVG path.
+ * @param points An array of Point objects.
+ */
+SvgPath__prototype.g/*addPolygon*/ = function addPolygon (points) {
+ var dataString = "";
+ for (var i = 0; i < points.length; i++) {
+ dataString += (i ? "L" : "M") + svgValue(points[i].x) + " " + svgValue(points[i].y);
+ }
+ this.B/*dataString*/ += dataString + "Z";
+};
+
+/**
+ * Adds a circle with the current fill color to the SVG path.
+ * @param {Point} point The upper left corner of the circle bounding box.
+ * @param {number} diameter The diameter of the circle.
+ * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path).
+ */
+SvgPath__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) {
+ var sweepFlag = counterClockwise ? 0 : 1,
+ svgRadius = svgValue(diameter / 2),
+ svgDiameter = svgValue(diameter),
+ svgArc = "a" + svgRadius + "," + svgRadius + " 0 1," + sweepFlag + " ";
+
+ this.B/*dataString*/ +=
+ "M" + svgValue(point.x) + " " + svgValue(point.y + diameter / 2) +
+ svgArc + svgDiameter + ",0" +
+ svgArc + (-svgDiameter) + ",0";
+};
+
+
+
+/**
+ * Renderer producing SVG output.
+ * @implements {Renderer}
+ */
+function SvgRenderer(target) {
+ /**
+ * @type {SvgPath}
+ * @private
+ */
+ this.C/*_path*/;
+
+ /**
+ * @type {Object.<string,SvgPath>}
+ * @private
+ */
+ this.D/*_pathsByColor*/ = { };
+
+ /**
+ * @type {SvgElement|SvgWriter}
+ * @private
+ */
+ this.R/*_target*/ = target;
+
+ /**
+ * @type {number}
+ */
+ this.k/*iconSize*/ = target.k/*iconSize*/;
+}
+var SvgRenderer__prototype = SvgRenderer.prototype;
+
+/**
+ * Fills the background with the specified color.
+ * @param {string} fillColor Fill color on the format #rrggbb[aa].
+ */
+SvgRenderer__prototype.m/*setBackground*/ = function setBackground (fillColor) {
+ var match = /^(#......)(..)?/.exec(fillColor),
+ opacity = match[2] ? parseHex(match[2], 0) / 255 : 1;
+ this.R/*_target*/.m/*setBackground*/(match[1], opacity);
+};
+
+/**
+ * Marks the beginning of a new shape of the specified color. Should be ended with a call to endShape.
+ * @param {string} color Fill color on format #xxxxxx.
+ */
+SvgRenderer__prototype.O/*beginShape*/ = function beginShape (color) {
+ this.C/*_path*/ = this.D/*_pathsByColor*/[color] || (this.D/*_pathsByColor*/[color] = new SvgPath());
+};
+
+/**
+ * Marks the end of the currently drawn shape.
+ */
+SvgRenderer__prototype.P/*endShape*/ = function endShape () { };
+
+/**
+ * Adds a polygon with the current fill color to the SVG.
+ * @param points An array of Point objects.
+ */
+SvgRenderer__prototype.g/*addPolygon*/ = function addPolygon (points) {
+ this.C/*_path*/.g/*addPolygon*/(points);
+};
+
+/**
+ * Adds a circle with the current fill color to the SVG.
+ * @param {Point} point The upper left corner of the circle bounding box.
+ * @param {number} diameter The diameter of the circle.
+ * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path).
+ */
+SvgRenderer__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) {
+ this.C/*_path*/.h/*addCircle*/(point, diameter, counterClockwise);
+};
+
+/**
+ * Called when the icon has been completely drawn.
+ */
+SvgRenderer__prototype.finish = function finish () {
+ var this$1 = this;
+
+ var pathsByColor = this.D/*_pathsByColor*/;
+ for (var color in pathsByColor) {
+ // hasOwnProperty cannot be shadowed in pathsByColor
+ // eslint-disable-next-line no-prototype-builtins
+ if (pathsByColor.hasOwnProperty(color)) {
+ this$1.R/*_target*/.S/*appendPath*/(color, pathsByColor[color].B/*dataString*/);
+ }
+ }
+};
+
+var SVG_CONSTANTS = {
+ T/*XMLNS*/: "http://www.w3.org/2000/svg",
+ U/*WIDTH*/: "width",
+ V/*HEIGHT*/: "height",
+};
+
+/**
+ * Renderer producing SVG output.
+ */
+function SvgWriter(iconSize) {
+ /**
+ * @type {number}
+ */
+ this.k/*iconSize*/ = iconSize;
+
+ /**
+ * @type {string}
+ * @private
+ */
+ this.F/*_s*/ =
+ '<svg xmlns="' + SVG_CONSTANTS.T/*XMLNS*/ + '" width="' +
+ iconSize + '" height="' + iconSize + '" viewBox="0 0 ' +
+ iconSize + ' ' + iconSize + '">';
+}
+var SvgWriter__prototype = SvgWriter.prototype;
+
+/**
+ * Fills the background with the specified color.
+ * @param {string} fillColor Fill color on the format #rrggbb.
+ * @param {number} opacity Opacity in the range [0.0, 1.0].
+ */
+SvgWriter__prototype.m/*setBackground*/ = function setBackground (fillColor, opacity) {
+ if (opacity) {
+ this.F/*_s*/ += '<rect width="100%" height="100%" fill="' +
+ fillColor + '" opacity="' + opacity.toFixed(2) + '"/>';
+ }
+};
+
+/**
+ * Writes a path to the SVG string.
+ * @param {string} color Fill color on format #rrggbb.
+ * @param {string} dataString The SVG path data string.
+ */
+SvgWriter__prototype.S/*appendPath*/ = function appendPath (color, dataString) {
+ this.F/*_s*/ += '<path fill="' + color + '" d="' + dataString + '"/>';
+};
+
+/**
+ * Gets the rendered image as an SVG string.
+ */
+SvgWriter__prototype.toString = function toString () {
+ return this.F/*_s*/ + "</svg>";
+};
+
+/**
+ * Draws an identicon as an SVG string.
+ * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon.
+ * @param {number} size - Icon size in pixels.
+ * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any
+ * global configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be
+ * specified in place of a configuration object.
+ * @returns {string} SVG string
+ */
+function toSvg(hashOrValue, size, config) {
+ var writer = new SvgWriter(size);
+ iconGenerator(new SvgRenderer(writer),
+ isValidHash(hashOrValue) || computeHash(hashOrValue),
+ config);
+ return writer.toString();
+}
+
+/**
+ * Creates a new element and adds it to the specified parent.
+ * @param {Element} parentNode
+ * @param {string} name
+ * @param {...(string|number)} keyValuePairs
+ */
+function SvgElement_append(parentNode, name) {
+ var keyValuePairs = [], len = arguments.length - 2;
+ while ( len-- > 0 ) keyValuePairs[ len ] = arguments[ len + 2 ];
+
+ var el = document.createElementNS(SVG_CONSTANTS.T/*XMLNS*/, name);
+
+ for (var i = 0; i + 1 < keyValuePairs.length; i += 2) {
+ el.setAttribute(
+ /** @type {string} */(keyValuePairs[i]),
+ /** @type {string} */(keyValuePairs[i + 1])
+ );
+ }
+
+ parentNode.appendChild(el);
+}
+
+
+/**
+ * Renderer producing SVG output.
+ */
+function SvgElement(element) {
+ // Don't use the clientWidth and clientHeight properties on SVG elements
+ // since Firefox won't serve a proper value of these properties on SVG
+ // elements (https://bugzilla.mozilla.org/show_bug.cgi?id=874811)
+ // Instead use 100px as a hardcoded size (the svg viewBox will rescale
+ // the icon to the correct dimensions)
+ var iconSize = this.k/*iconSize*/ = Math.min(
+ (Number(element.getAttribute(SVG_CONSTANTS.U/*WIDTH*/)) || 100),
+ (Number(element.getAttribute(SVG_CONSTANTS.V/*HEIGHT*/)) || 100)
+ );
+
+ /**
+ * @type {Element}
+ * @private
+ */
+ this.W/*_el*/ = element;
+
+ // Clear current SVG child elements
+ while (element.firstChild) {
+ element.removeChild(element.firstChild);
+ }
+
+ // Set viewBox attribute to ensure the svg scales nicely.
+ element.setAttribute("viewBox", "0 0 " + iconSize + " " + iconSize);
+ element.setAttribute("preserveAspectRatio", "xMidYMid meet");
+}
+var SvgElement__prototype = SvgElement.prototype;
+
+/**
+ * Fills the background with the specified color.
+ * @param {string} fillColor Fill color on the format #rrggbb.
+ * @param {number} opacity Opacity in the range [0.0, 1.0].
+ */
+SvgElement__prototype.m/*setBackground*/ = function setBackground (fillColor, opacity) {
+ if (opacity) {
+ SvgElement_append(this.W/*_el*/, "rect",
+ SVG_CONSTANTS.U/*WIDTH*/, "100%",
+ SVG_CONSTANTS.V/*HEIGHT*/, "100%",
+ "fill", fillColor,
+ "opacity", opacity);
+ }
+};
+
+/**
+ * Appends a path to the SVG element.
+ * @param {string} color Fill color on format #xxxxxx.
+ * @param {string} dataString The SVG path data string.
+ */
+SvgElement__prototype.S/*appendPath*/ = function appendPath (color, dataString) {
+ SvgElement_append(this.W/*_el*/, "path",
+ "fill", color,
+ "d", dataString);
+};
+
+/**
+ * Updates all canvas elements with the `data-jdenticon-hash` or `data-jdenticon-value` attribute.
+ */
+function updateAll() {
+ if (documentQuerySelectorAll) {
+ update(ICON_SELECTOR);
+ }
+}
+
+/**
+ * Updates all canvas elements with the `data-jdenticon-hash` or `data-jdenticon-value` attribute that have not already
+ * been rendered.
+ */
+function updateAllConditional() {
+ if (documentQuerySelectorAll) {
+ /** @type {NodeListOf<HTMLElement>} */
+ var elements = documentQuerySelectorAll(ICON_SELECTOR);
+
+ for (var i = 0; i < elements.length; i++) {
+ var el = elements[i];
+ if (!el[IS_RENDERED_PROPERTY]) {
+ update(el);
+ }
+ }
+ }
+}
+
+/**
+ * Updates the identicon in the specified `<canvas>` or `<svg>` elements.
+ * @param {(string|Element)} el - Specifies the container in which the icon is rendered as a DOM element of the type
+ * `<svg>` or `<canvas>`, or a CSS selector to such an element.
+ * @param {*=} hashOrValue - Optional hash or value to be rendered. If not specified, the `data-jdenticon-hash` or
+ * `data-jdenticon-value` attribute will be evaluated.
+ * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any
+ * global configuration in its entirety. For backward compability a padding value in the range [0.0, 0.5) can be
+ * specified in place of a configuration object.
+ */
+function update(el, hashOrValue, config) {
+ renderDomElement(el, hashOrValue, config, function (el, iconType) {
+ if (iconType) {
+ return iconType == ICON_TYPE_SVG ?
+ new SvgRenderer(new SvgElement(el)) :
+ new CanvasRenderer(/** @type {HTMLCanvasElement} */(el).getContext("2d"));
+ }
+ });
+}
+
+/**
+ * Updates the identicon in the specified canvas or svg elements.
+ * @param {(string|Element)} el - Specifies the container in which the icon is rendered as a DOM element of the type
+ * `<svg>` or `<canvas>`, or a CSS selector to such an element.
+ * @param {*} hashOrValue - Optional hash or value to be rendered. If not specified, the `data-jdenticon-hash` or
+ * `data-jdenticon-value` attribute will be evaluated.
+ * @param {Object|number|undefined} config
+ * @param {function(Element,number):Renderer} rendererFactory - Factory function for creating an icon renderer.
+ */
+function renderDomElement(el, hashOrValue, config, rendererFactory) {
+ if (typeof el === "string") {
+ if (documentQuerySelectorAll) {
+ var elements = documentQuerySelectorAll(el);
+ for (var i = 0; i < elements.length; i++) {
+ renderDomElement(elements[i], hashOrValue, config, rendererFactory);
+ }
+ }
+ return;
+ }
+
+ // Hash selection. The result from getValidHash or computeHash is
+ // accepted as a valid hash.
+ var hash =
+ // 1. Explicit valid hash
+ isValidHash(hashOrValue) ||
+
+ // 2. Explicit value (`!= null` catches both null and undefined)
+ hashOrValue != null && computeHash(hashOrValue) ||
+
+ // 3. `data-jdenticon-hash` attribute
+ isValidHash(el.getAttribute(ATTRIBUTES.t/*HASH*/)) ||
+
+ // 4. `data-jdenticon-value` attribute.
+ // We want to treat an empty attribute as an empty value.
+ // Some browsers return empty string even if the attribute
+ // is not specified, so use hasAttribute to determine if
+ // the attribute is specified.
+ el.hasAttribute(ATTRIBUTES.o/*VALUE*/) && computeHash(el.getAttribute(ATTRIBUTES.o/*VALUE*/));
+
+ if (!hash) {
+ // No hash specified. Don't render an icon.
+ return;
+ }
+
+ var renderer = rendererFactory(el, getIdenticonType(el));
+ if (renderer) {
+ // Draw icon
+ iconGenerator(renderer, hash, config);
+ el[IS_RENDERED_PROPERTY] = true;
+ }
+}
+
+/**
+ * Renders an identicon for all matching supported elements.
+ *
+ * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon. If not
+ * specified the `data-jdenticon-hash` and `data-jdenticon-value` attributes of each element will be
+ * evaluated.
+ * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any global
+ * configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be
+ * specified in place of a configuration object.
+ */
+function jdenticonJqueryPlugin(hashOrValue, config) {
+ this["each"](function (index, el) {
+ update(el, hashOrValue, config);
+ });
+ return this;
+}
+
+// This file is compiled to dist/jdenticon.js and dist/jdenticon.min.js
+
+var jdenticon = updateAll;
+
+defineConfigProperty(jdenticon);
+
+// Export public API
+jdenticon["configure"] = configure;
+jdenticon["drawIcon"] = drawIcon;
+jdenticon["toSvg"] = toSvg;
+jdenticon["update"] = update;
+jdenticon["updateCanvas"] = update;
+jdenticon["updateSvg"] = update;
+
+/**
+ * Specifies the version of the Jdenticon package in use.
+ * @type {string}
+ */
+jdenticon["version"] = "3.3.0";
+
+/**
+ * Specifies which bundle of Jdenticon that is used.
+ * @type {string}
+ */
+jdenticon["bundle"] = "browser-umd";
+
+// Basic jQuery plugin
+var jQuery = GLOBAL["jQuery"];
+if (jQuery) {
+ jQuery["fn"]["jdenticon"] = jdenticonJqueryPlugin;
+}
+
+/**
+ * This function is called once upon page load.
+ */
+function jdenticonStartup() {
+ var replaceMode = (
+ jdenticon[CONFIG_PROPERTIES.n/*MODULE*/] ||
+ GLOBAL[CONFIG_PROPERTIES.G/*GLOBAL*/] ||
+ { }
+ )["replaceMode"];
+
+ if (replaceMode != "never") {
+ updateAllConditional();
+
+ if (replaceMode == "observe") {
+ observer(update);
+ }
+ }
+}
+
+// Schedule to render all identicons on the page once it has been loaded.
+whenDocumentIsReady(jdenticonStartup);
+
+return jdenticon;
+
+});
+\ No newline at end of file
diff --git a/src/static/scripts/jdenticon.js b/src/static/scripts/jdenticon.js
@@ -1,1462 +0,0 @@
-/**
- * Jdenticon 3.2.0
- * http://jdenticon.com
- *
- * Built: 2022-08-07T11:23:11.640Z
- *
- * MIT License
- *
- * Copyright (c) 2014-2021 Daniel Mester Pirttijärvi
- *
- * 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 (umdGlobal, factory) {
- var jdenticon = factory(umdGlobal);
-
- // Node.js
- if (typeof module !== "undefined" && "exports" in module) {
- module["exports"] = jdenticon;
- }
- // RequireJS
- else if (typeof define === "function" && define["amd"]) {
- define([], function () { return jdenticon; });
- }
- // No module loader
- else {
- umdGlobal["jdenticon"] = jdenticon;
- }
-})(typeof self !== "undefined" ? self : this, function (umdGlobal) {
-'use strict';
-
-/**
- * Parses a substring of the hash as a number.
- * @param {number} startPosition
- * @param {number=} octets
- */
-function parseHex(hash, startPosition, octets) {
- return parseInt(hash.substr(startPosition, octets), 16);
-}
-
-function decToHex(v) {
- v |= 0; // Ensure integer value
- return v < 0 ? "00" :
- v < 16 ? "0" + v.toString(16) :
- v < 256 ? v.toString(16) :
- "ff";
-}
-
-function hueToRgb(m1, m2, h) {
- h = h < 0 ? h + 6 : h > 6 ? h - 6 : h;
- return decToHex(255 * (
- h < 1 ? m1 + (m2 - m1) * h :
- h < 3 ? m2 :
- h < 4 ? m1 + (m2 - m1) * (4 - h) :
- m1));
-}
-
-/**
- * @param {string} color Color value to parse. Currently hexadecimal strings on the format #rgb[a] and #rrggbb[aa] are supported.
- * @returns {string}
- */
-function parseColor(color) {
- if (/^#[0-9a-f]{3,8}$/i.test(color)) {
- var result;
- var colorLength = color.length;
-
- if (colorLength < 6) {
- var r = color[1],
- g = color[2],
- b = color[3],
- a = color[4] || "";
- result = "#" + r + r + g + g + b + b + a + a;
- }
- if (colorLength == 7 || colorLength > 8) {
- result = color;
- }
-
- return result;
- }
-}
-
-/**
- * Converts a hexadecimal color to a CSS3 compatible color.
- * @param {string} hexColor Color on the format "#RRGGBB" or "#RRGGBBAA"
- * @returns {string}
- */
-function toCss3Color(hexColor) {
- var a = parseHex(hexColor, 7, 2);
- var result;
-
- if (isNaN(a)) {
- result = hexColor;
- } else {
- var r = parseHex(hexColor, 1, 2),
- g = parseHex(hexColor, 3, 2),
- b = parseHex(hexColor, 5, 2);
- result = "rgba(" + r + "," + g + "," + b + "," + (a / 255).toFixed(2) + ")";
- }
-
- return result;
-}
-
-/**
- * Converts an HSL color to a hexadecimal RGB color.
- * @param {number} hue Hue in range [0, 1]
- * @param {number} saturation Saturation in range [0, 1]
- * @param {number} lightness Lightness in range [0, 1]
- * @returns {string}
- */
-function hsl(hue, saturation, lightness) {
- // Based on http://www.w3.org/TR/2011/REC-css3-color-20110607/#hsl-color
- var result;
-
- if (saturation == 0) {
- var partialHex = decToHex(lightness * 255);
- result = partialHex + partialHex + partialHex;
- }
- else {
- var m2 = lightness <= 0.5 ? lightness * (saturation + 1) : lightness + saturation - lightness * saturation,
- m1 = lightness * 2 - m2;
- result =
- hueToRgb(m1, m2, hue * 6 + 2) +
- hueToRgb(m1, m2, hue * 6) +
- hueToRgb(m1, m2, hue * 6 - 2);
- }
-
- return "#" + result;
-}
-
-/**
- * Converts an HSL color to a hexadecimal RGB color. This function will correct the lightness for the "dark" hues
- * @param {number} hue Hue in range [0, 1]
- * @param {number} saturation Saturation in range [0, 1]
- * @param {number} lightness Lightness in range [0, 1]
- * @returns {string}
- */
-function correctedHsl(hue, saturation, lightness) {
- // The corrector specifies the perceived middle lightness for each hue
- var correctors = [ 0.55, 0.5, 0.5, 0.46, 0.6, 0.55, 0.55 ],
- corrector = correctors[(hue * 6 + 0.5) | 0];
-
- // Adjust the input lightness relative to the corrector
- lightness = lightness < 0.5 ? lightness * corrector * 2 : corrector + (lightness - 0.5) * (1 - corrector) * 2;
-
- return hsl(hue, saturation, lightness);
-}
-
-/* global umdGlobal */
-
-// In the future we can replace `GLOBAL` with `globalThis`, but for now use the old school global detection for
-// backward compatibility.
-var GLOBAL = umdGlobal;
-
-/**
- * @typedef {Object} ParsedConfiguration
- * @property {number} colorSaturation
- * @property {number} grayscaleSaturation
- * @property {string} backColor
- * @property {number} iconPadding
- * @property {function(number):number} hue
- * @property {function(number):number} colorLightness
- * @property {function(number):number} grayscaleLightness
- */
-
-var CONFIG_PROPERTIES = {
- G/*GLOBAL*/: "jdenticon_config",
- n/*MODULE*/: "config",
-};
-
-var rootConfigurationHolder = {};
-
-/**
- * Defines the deprecated `config` property on the root Jdenticon object without printing a warning in the console
- * when it is being used.
- * @param {!Object} rootObject
- */
-function defineConfigProperty(rootObject) {
- rootConfigurationHolder = rootObject;
-}
-
-/**
- * Sets a new icon style configuration. The new configuration is not merged with the previous one. *
- * @param {Object} newConfiguration - New configuration object.
- */
-function configure(newConfiguration) {
- if (arguments.length) {
- rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/] = newConfiguration;
- }
- return rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/];
-}
-
-/**
- * Gets the normalized current Jdenticon color configuration. Missing fields have default values.
- * @param {Object|number|undefined} paddingOrLocalConfig - Configuration passed to the called API method. A
- * local configuration overrides the global configuration in it entirety. This parameter can for backward
- * compatibility also contain a padding value. A padding value only overrides the global padding, not the
- * entire global configuration.
- * @param {number} defaultPadding - Padding used if no padding is specified in neither the configuration nor
- * explicitly to the API method.
- * @returns {ParsedConfiguration}
- */
-function getConfiguration(paddingOrLocalConfig, defaultPadding) {
- var configObject =
- typeof paddingOrLocalConfig == "object" && paddingOrLocalConfig ||
- rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/] ||
- GLOBAL[CONFIG_PROPERTIES.G/*GLOBAL*/] ||
- { },
-
- lightnessConfig = configObject["lightness"] || { },
-
- // In versions < 2.1.0 there was no grayscale saturation -
- // saturation was the color saturation.
- saturation = configObject["saturation"] || { },
- colorSaturation = "color" in saturation ? saturation["color"] : saturation,
- grayscaleSaturation = saturation["grayscale"],
-
- backColor = configObject["backColor"],
- padding = configObject["padding"];
-
- /**
- * Creates a lightness range.
- */
- function lightness(configName, defaultRange) {
- var range = lightnessConfig[configName];
-
- // Check if the lightness range is an array-like object. This way we ensure the
- // array contain two values at the same time.
- if (!(range && range.length > 1)) {
- range = defaultRange;
- }
-
- /**
- * Gets a lightness relative the specified value in the specified lightness range.
- */
- return function (value) {
- value = range[0] + value * (range[1] - range[0]);
- return value < 0 ? 0 : value > 1 ? 1 : value;
- };
- }
-
- /**
- * Gets a hue allowed by the configured hue restriction,
- * provided the originally computed hue.
- */
- function hueFunction(originalHue) {
- var hueConfig = configObject["hues"];
- var hue;
-
- // Check if 'hues' is an array-like object. This way we also ensure that
- // the array is not empty, which would mean no hue restriction.
- if (hueConfig && hueConfig.length > 0) {
- // originalHue is in the range [0, 1]
- // Multiply with 0.999 to change the range to [0, 1) and then truncate the index.
- hue = hueConfig[0 | (0.999 * originalHue * hueConfig.length)];
- }
-
- return typeof hue == "number" ?
-
- // A hue was specified. We need to convert the hue from
- // degrees on any turn - e.g. 746° is a perfectly valid hue -
- // to turns in the range [0, 1).
- ((((hue / 360) % 1) + 1) % 1) :
-
- // No hue configured => use original hue
- originalHue;
- }
-
- return {
- X/*hue*/: hueFunction,
- p/*colorSaturation*/: typeof colorSaturation == "number" ? colorSaturation : 0.5,
- H/*grayscaleSaturation*/: typeof grayscaleSaturation == "number" ? grayscaleSaturation : 0,
- q/*colorLightness*/: lightness("color", [0.4, 0.8]),
- I/*grayscaleLightness*/: lightness("grayscale", [0.3, 0.9]),
- J/*backColor*/: parseColor(backColor),
- Y/*iconPadding*/:
- typeof paddingOrLocalConfig == "number" ? paddingOrLocalConfig :
- typeof padding == "number" ? padding :
- defaultPadding
- }
-}
-
-var ICON_TYPE_SVG = 1;
-
-var ICON_TYPE_CANVAS = 2;
-
-var ATTRIBUTES = {
- t/*HASH*/: "data-jdenticon-hash",
- o/*VALUE*/: "data-jdenticon-value"
-};
-
-var ICON_SELECTOR = "[" + ATTRIBUTES.t/*HASH*/ +"],[" + ATTRIBUTES.o/*VALUE*/ +"]";
-
-var documentQuerySelectorAll = /** @type {!Function} */ (
- typeof document !== "undefined" && document.querySelectorAll.bind(document));
-
-function getIdenticonType(el) {
- if (el) {
- var tagName = el["tagName"];
-
- if (/^svg$/i.test(tagName)) {
- return ICON_TYPE_SVG;
- }
-
- if (/^canvas$/i.test(tagName) && "getContext" in el) {
- return ICON_TYPE_CANVAS;
- }
- }
-}
-
-function observer(updateCallback) {
- if (typeof MutationObserver != "undefined") {
- var mutationObserver = new MutationObserver(function onmutation(mutations) {
- for (var mutationIndex = 0; mutationIndex < mutations.length; mutationIndex++) {
- var mutation = mutations[mutationIndex];
- var addedNodes = mutation.addedNodes;
-
- for (var addedNodeIndex = 0; addedNodes && addedNodeIndex < addedNodes.length; addedNodeIndex++) {
- var addedNode = addedNodes[addedNodeIndex];
-
- // Skip other types of nodes than element nodes, since they might not support
- // the querySelectorAll method => runtime error.
- if (addedNode.nodeType == 1) {
- if (getIdenticonType(addedNode)) {
- updateCallback(addedNode);
- }
- else {
- var icons = /** @type {Element} */(addedNode).querySelectorAll(ICON_SELECTOR);
- for (var iconIndex = 0; iconIndex < icons.length; iconIndex++) {
- updateCallback(icons[iconIndex]);
- }
- }
- }
- }
-
- if (mutation.type == "attributes" && getIdenticonType(mutation.target)) {
- updateCallback(mutation.target);
- }
- }
- });
-
- mutationObserver.observe(document.body, {
- "childList": true,
- "attributes": true,
- "attributeFilter": [ATTRIBUTES.o/*VALUE*/, ATTRIBUTES.t/*HASH*/, "width", "height"],
- "subtree": true,
- });
- }
-}
-
-/**
- * Represents a point.
- */
-function Point(x, y) {
- this.x = x;
- this.y = y;
-}
-
-/**
- * Translates and rotates a point before being passed on to the canvas context. This was previously done by the canvas context itself,
- * but this caused a rendering issue in Chrome on sizes > 256 where the rotation transformation of inverted paths was not done properly.
- */
-function Transform(x, y, size, rotation) {
- this.u/*_x*/ = x;
- this.v/*_y*/ = y;
- this.K/*_size*/ = size;
- this.Z/*_rotation*/ = rotation;
-}
-
-/**
- * Transforms the specified point based on the translation and rotation specification for this Transform.
- * @param {number} x x-coordinate
- * @param {number} y y-coordinate
- * @param {number=} w The width of the transformed rectangle. If greater than 0, this will ensure the returned point is of the upper left corner of the transformed rectangle.
- * @param {number=} h The height of the transformed rectangle. If greater than 0, this will ensure the returned point is of the upper left corner of the transformed rectangle.
- */
-Transform.prototype.L/*transformIconPoint*/ = function transformIconPoint (x, y, w, h) {
- var right = this.u/*_x*/ + this.K/*_size*/,
- bottom = this.v/*_y*/ + this.K/*_size*/,
- rotation = this.Z/*_rotation*/;
- return rotation === 1 ? new Point(right - y - (h || 0), this.v/*_y*/ + x) :
- rotation === 2 ? new Point(right - x - (w || 0), bottom - y - (h || 0)) :
- rotation === 3 ? new Point(this.u/*_x*/ + y, bottom - x - (w || 0)) :
- new Point(this.u/*_x*/ + x, this.v/*_y*/ + y);
-};
-
-var NO_TRANSFORM = new Transform(0, 0, 0, 0);
-
-
-
-/**
- * Provides helper functions for rendering common basic shapes.
- */
-function Graphics(renderer) {
- /**
- * @type {Renderer}
- * @private
- */
- this.M/*_renderer*/ = renderer;
-
- /**
- * @type {Transform}
- */
- this.A/*currentTransform*/ = NO_TRANSFORM;
-}
-var Graphics__prototype = Graphics.prototype;
-
-/**
- * Adds a polygon to the underlying renderer.
- * @param {Array<number>} points The points of the polygon clockwise on the format [ x0, y0, x1, y1, ..., xn, yn ]
- * @param {boolean=} invert Specifies if the polygon will be inverted.
- */
-Graphics__prototype.g/*addPolygon*/ = function addPolygon (points, invert) {
- var this$1 = this;
-
- var di = invert ? -2 : 2,
- transformedPoints = [];
-
- for (var i = invert ? points.length - 2 : 0; i < points.length && i >= 0; i += di) {
- transformedPoints.push(this$1.A/*currentTransform*/.L/*transformIconPoint*/(points[i], points[i + 1]));
- }
-
- this.M/*_renderer*/.g/*addPolygon*/(transformedPoints);
-};
-
-/**
- * Adds a polygon to the underlying renderer.
- * Source: http://stackoverflow.com/a/2173084
- * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the entire ellipse.
- * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the entire ellipse.
- * @param {number} size The size of the ellipse.
- * @param {boolean=} invert Specifies if the ellipse will be inverted.
- */
-Graphics__prototype.h/*addCircle*/ = function addCircle (x, y, size, invert) {
- var p = this.A/*currentTransform*/.L/*transformIconPoint*/(x, y, size, size);
- this.M/*_renderer*/.h/*addCircle*/(p, size, invert);
-};
-
-/**
- * Adds a rectangle to the underlying renderer.
- * @param {number} x The x-coordinate of the upper left corner of the rectangle.
- * @param {number} y The y-coordinate of the upper left corner of the rectangle.
- * @param {number} w The width of the rectangle.
- * @param {number} h The height of the rectangle.
- * @param {boolean=} invert Specifies if the rectangle will be inverted.
- */
-Graphics__prototype.i/*addRectangle*/ = function addRectangle (x, y, w, h, invert) {
- this.g/*addPolygon*/([
- x, y,
- x + w, y,
- x + w, y + h,
- x, y + h
- ], invert);
-};
-
-/**
- * Adds a right triangle to the underlying renderer.
- * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the triangle.
- * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the triangle.
- * @param {number} w The width of the triangle.
- * @param {number} h The height of the triangle.
- * @param {number} r The rotation of the triangle (clockwise). 0 = right corner of the triangle in the lower left corner of the bounding rectangle.
- * @param {boolean=} invert Specifies if the triangle will be inverted.
- */
-Graphics__prototype.j/*addTriangle*/ = function addTriangle (x, y, w, h, r, invert) {
- var points = [
- x + w, y,
- x + w, y + h,
- x, y + h,
- x, y
- ];
- points.splice(((r || 0) % 4) * 2, 2);
- this.g/*addPolygon*/(points, invert);
-};
-
-/**
- * Adds a rhombus to the underlying renderer.
- * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the rhombus.
- * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the rhombus.
- * @param {number} w The width of the rhombus.
- * @param {number} h The height of the rhombus.
- * @param {boolean=} invert Specifies if the rhombus will be inverted.
- */
-Graphics__prototype.N/*addRhombus*/ = function addRhombus (x, y, w, h, invert) {
- this.g/*addPolygon*/([
- x + w / 2, y,
- x + w, y + h / 2,
- x + w / 2, y + h,
- x, y + h / 2
- ], invert);
-};
-
-/**
- * @param {number} index
- * @param {Graphics} g
- * @param {number} cell
- * @param {number} positionIndex
- */
-function centerShape(index, g, cell, positionIndex) {
- index = index % 14;
-
- var k, m, w, h, inner, outer;
-
- !index ? (
- k = cell * 0.42,
- g.g/*addPolygon*/([
- 0, 0,
- cell, 0,
- cell, cell - k * 2,
- cell - k, cell,
- 0, cell
- ])) :
-
- index == 1 ? (
- w = 0 | (cell * 0.5),
- h = 0 | (cell * 0.8),
-
- g.j/*addTriangle*/(cell - w, 0, w, h, 2)) :
-
- index == 2 ? (
- w = 0 | (cell / 3),
- g.i/*addRectangle*/(w, w, cell - w, cell - w)) :
-
- index == 3 ? (
- inner = cell * 0.1,
- // Use fixed outer border widths in small icons to ensure the border is drawn
- outer =
- cell < 6 ? 1 :
- cell < 8 ? 2 :
- (0 | (cell * 0.25)),
-
- inner =
- inner > 1 ? (0 | inner) : // large icon => truncate decimals
- inner > 0.5 ? 1 : // medium size icon => fixed width
- inner, // small icon => anti-aliased border
-
- g.i/*addRectangle*/(outer, outer, cell - inner - outer, cell - inner - outer)) :
-
- index == 4 ? (
- m = 0 | (cell * 0.15),
- w = 0 | (cell * 0.5),
- g.h/*addCircle*/(cell - w - m, cell - w - m, w)) :
-
- index == 5 ? (
- inner = cell * 0.1,
- outer = inner * 4,
-
- // Align edge to nearest pixel in large icons
- outer > 3 && (outer = 0 | outer),
-
- g.i/*addRectangle*/(0, 0, cell, cell),
- g.g/*addPolygon*/([
- outer, outer,
- cell - inner, outer,
- outer + (cell - outer - inner) / 2, cell - inner
- ], true)) :
-
- index == 6 ?
- g.g/*addPolygon*/([
- 0, 0,
- cell, 0,
- cell, cell * 0.7,
- cell * 0.4, cell * 0.4,
- cell * 0.7, cell,
- 0, cell
- ]) :
-
- index == 7 ?
- g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 3) :
-
- index == 8 ? (
- g.i/*addRectangle*/(0, 0, cell, cell / 2),
- g.i/*addRectangle*/(0, cell / 2, cell / 2, cell / 2),
- g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 1)) :
-
- index == 9 ? (
- inner = cell * 0.14,
- // Use fixed outer border widths in small icons to ensure the border is drawn
- outer =
- cell < 4 ? 1 :
- cell < 6 ? 2 :
- (0 | (cell * 0.35)),
-
- inner =
- cell < 8 ? inner : // small icon => anti-aliased border
- (0 | inner), // large icon => truncate decimals
-
- g.i/*addRectangle*/(0, 0, cell, cell),
- g.i/*addRectangle*/(outer, outer, cell - outer - inner, cell - outer - inner, true)) :
-
- index == 10 ? (
- inner = cell * 0.12,
- outer = inner * 3,
-
- g.i/*addRectangle*/(0, 0, cell, cell),
- g.h/*addCircle*/(outer, outer, cell - inner - outer, true)) :
-
- index == 11 ?
- g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 3) :
-
- index == 12 ? (
- m = cell * 0.25,
- g.i/*addRectangle*/(0, 0, cell, cell),
- g.N/*addRhombus*/(m, m, cell - m, cell - m, true)) :
-
- // 13
- (
- !positionIndex && (
- m = cell * 0.4, w = cell * 1.2,
- g.h/*addCircle*/(m, m, w)
- )
- );
-}
-
-/**
- * @param {number} index
- * @param {Graphics} g
- * @param {number} cell
- */
-function outerShape(index, g, cell) {
- index = index % 4;
-
- var m;
-
- !index ?
- g.j/*addTriangle*/(0, 0, cell, cell, 0) :
-
- index == 1 ?
- g.j/*addTriangle*/(0, cell / 2, cell, cell / 2, 0) :
-
- index == 2 ?
- g.N/*addRhombus*/(0, 0, cell, cell) :
-
- // 3
- (
- m = cell / 6,
- g.h/*addCircle*/(m, m, cell - 2 * m)
- );
-}
-
-/**
- * Gets a set of identicon color candidates for a specified hue and config.
- * @param {number} hue
- * @param {ParsedConfiguration} config
- */
-function colorTheme(hue, config) {
- hue = config.X/*hue*/(hue);
- return [
- // Dark gray
- correctedHsl(hue, config.H/*grayscaleSaturation*/, config.I/*grayscaleLightness*/(0)),
- // Mid color
- correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(0.5)),
- // Light gray
- correctedHsl(hue, config.H/*grayscaleSaturation*/, config.I/*grayscaleLightness*/(1)),
- // Light color
- correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(1)),
- // Dark color
- correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(0))
- ];
-}
-
-/**
- * Draws an identicon to a specified renderer.
- * @param {Renderer} renderer
- * @param {string} hash
- * @param {Object|number=} config
- */
-function iconGenerator(renderer, hash, config) {
- var parsedConfig = getConfiguration(config, 0.08);
-
- // Set background color
- if (parsedConfig.J/*backColor*/) {
- renderer.m/*setBackground*/(parsedConfig.J/*backColor*/);
- }
-
- // Calculate padding and round to nearest integer
- var size = renderer.k/*iconSize*/;
- var padding = (0.5 + size * parsedConfig.Y/*iconPadding*/) | 0;
- size -= padding * 2;
-
- var graphics = new Graphics(renderer);
-
- // Calculate cell size and ensure it is an integer
- var cell = 0 | (size / 4);
-
- // Since the cell size is integer based, the actual icon will be slightly smaller than specified => center icon
- var x = 0 | (padding + size / 2 - cell * 2);
- var y = 0 | (padding + size / 2 - cell * 2);
-
- function renderShape(colorIndex, shapes, index, rotationIndex, positions) {
- var shapeIndex = parseHex(hash, index, 1);
- var r = rotationIndex ? parseHex(hash, rotationIndex, 1) : 0;
-
- renderer.O/*beginShape*/(availableColors[selectedColorIndexes[colorIndex]]);
-
- for (var i = 0; i < positions.length; i++) {
- graphics.A/*currentTransform*/ = new Transform(x + positions[i][0] * cell, y + positions[i][1] * cell, cell, r++ % 4);
- shapes(shapeIndex, graphics, cell, i);
- }
-
- renderer.P/*endShape*/();
- }
-
- // AVAILABLE COLORS
- var hue = parseHex(hash, -7) / 0xfffffff,
-
- // Available colors for this icon
- availableColors = colorTheme(hue, parsedConfig),
-
- // The index of the selected colors
- selectedColorIndexes = [];
-
- var index;
-
- function isDuplicate(values) {
- if (values.indexOf(index) >= 0) {
- for (var i = 0; i < values.length; i++) {
- if (selectedColorIndexes.indexOf(values[i]) >= 0) {
- return true;
- }
- }
- }
- }
-
- for (var i = 0; i < 3; i++) {
- index = parseHex(hash, 8 + i, 1) % availableColors.length;
- if (isDuplicate([0, 4]) || // Disallow dark gray and dark color combo
- isDuplicate([2, 3])) { // Disallow light gray and light color combo
- index = 1;
- }
- selectedColorIndexes.push(index);
- }
-
- // ACTUAL RENDERING
- // Sides
- renderShape(0, outerShape, 2, 3, [[1, 0], [2, 0], [2, 3], [1, 3], [0, 1], [3, 1], [3, 2], [0, 2]]);
- // Corners
- renderShape(1, outerShape, 4, 5, [[0, 0], [3, 0], [3, 3], [0, 3]]);
- // Center
- renderShape(2, centerShape, 1, null, [[1, 1], [2, 1], [2, 2], [1, 2]]);
-
- renderer.finish();
-}
-
-/**
- * Computes a SHA1 hash for any value and returns it as a hexadecimal string.
- *
- * This function is optimized for minimal code size and rather short messages.
- *
- * @param {string} message
- */
-function sha1(message) {
- var HASH_SIZE_HALF_BYTES = 40;
- var BLOCK_SIZE_WORDS = 16;
-
- // Variables
- // `var` is used to be able to minimize the number of `var` keywords.
- var i = 0,
- f = 0,
-
- // Use `encodeURI` to UTF8 encode the message without any additional libraries
- // We could use `unescape` + `encodeURI` to minimize the code, but that would be slightly risky
- // since `unescape` is deprecated.
- urlEncodedMessage = encodeURI(message) + "%80", // trailing '1' bit padding
-
- // This can be changed to a preallocated Uint32Array array for greater performance and larger code size
- data = [],
- dataSize,
-
- hashBuffer = [],
-
- a = 0x67452301,
- b = 0xefcdab89,
- c = ~a,
- d = ~b,
- e = 0xc3d2e1f0,
- hash = [a, b, c, d, e],
-
- blockStartIndex = 0,
- hexHash = "";
-
- /**
- * Rotates the value a specified number of bits to the left.
- * @param {number} value Value to rotate
- * @param {number} shift Bit count to shift.
- */
- function rotl(value, shift) {
- return (value << shift) | (value >>> (32 - shift));
- }
-
- // Message data
- for ( ; i < urlEncodedMessage.length; f++) {
- data[f >> 2] = data[f >> 2] |
- (
- (
- urlEncodedMessage[i] == "%"
- // Percent encoded byte
- ? parseInt(urlEncodedMessage.substring(i + 1, i += 3), 16)
- // Unencoded byte
- : urlEncodedMessage.charCodeAt(i++)
- )
-
- // Read bytes in reverse order (big endian words)
- << ((3 - (f & 3)) * 8)
- );
- }
-
- // f is now the length of the utf8 encoded message
- // 7 = 8 bytes (64 bit) for message size, -1 to round down
- // >> 6 = integer division with block size
- dataSize = (((f + 7) >> 6) + 1) * BLOCK_SIZE_WORDS;
-
- // Message size in bits.
- // SHA1 uses a 64 bit integer to represent the size, but since we only support short messages only the least
- // significant 32 bits are set. -8 is for the '1' bit padding byte.
- data[dataSize - 1] = f * 8 - 8;
-
- // Compute hash
- for ( ; blockStartIndex < dataSize; blockStartIndex += BLOCK_SIZE_WORDS) {
- for (i = 0; i < 80; i++) {
- f = rotl(a, 5) + e + (
- // Ch
- i < 20 ? ((b & c) ^ ((~b) & d)) + 0x5a827999 :
-
- // Parity
- i < 40 ? (b ^ c ^ d) + 0x6ed9eba1 :
-
- // Maj
- i < 60 ? ((b & c) ^ (b & d) ^ (c & d)) + 0x8f1bbcdc :
-
- // Parity
- (b ^ c ^ d) + 0xca62c1d6
- ) + (
- hashBuffer[i] = i < BLOCK_SIZE_WORDS
- // Bitwise OR is used to coerse `undefined` to 0
- ? (data[blockStartIndex + i] | 0)
- : rotl(hashBuffer[i - 3] ^ hashBuffer[i - 8] ^ hashBuffer[i - 14] ^ hashBuffer[i - 16], 1)
- );
-
- e = d;
- d = c;
- c = rotl(b, 30);
- b = a;
- a = f;
- }
-
- hash[0] = a = ((hash[0] + a) | 0);
- hash[1] = b = ((hash[1] + b) | 0);
- hash[2] = c = ((hash[2] + c) | 0);
- hash[3] = d = ((hash[3] + d) | 0);
- hash[4] = e = ((hash[4] + e) | 0);
- }
-
- // Format hex hash
- for (i = 0; i < HASH_SIZE_HALF_BYTES; i++) {
- hexHash += (
- (
- // Get word (2^3 half-bytes per word)
- hash[i >> 3] >>>
-
- // Append half-bytes in reverse order
- ((7 - (i & 7)) * 4)
- )
- // Clamp to half-byte
- & 0xf
- ).toString(16);
- }
-
- return hexHash;
-}
-
-/**
- * Inputs a value that might be a valid hash string for Jdenticon and returns it
- * if it is determined valid, otherwise a falsy value is returned.
- */
-function isValidHash(hashCandidate) {
- return /^[0-9a-f]{11,}$/i.test(hashCandidate) && hashCandidate;
-}
-
-/**
- * Computes a hash for the specified value. Currently SHA1 is used. This function
- * always returns a valid hash.
- */
-function computeHash(value) {
- return sha1(value == null ? "" : "" + value);
-}
-
-
-
-/**
- * Renderer redirecting drawing commands to a canvas context.
- * @implements {Renderer}
- */
-function CanvasRenderer(ctx, iconSize) {
- var canvas = ctx.canvas;
- var width = canvas.width;
- var height = canvas.height;
-
- ctx.save();
-
- if (!iconSize) {
- iconSize = Math.min(width, height);
-
- ctx.translate(
- ((width - iconSize) / 2) | 0,
- ((height - iconSize) / 2) | 0);
- }
-
- /**
- * @private
- */
- this.l/*_ctx*/ = ctx;
- this.k/*iconSize*/ = iconSize;
-
- ctx.clearRect(0, 0, iconSize, iconSize);
-}
-var CanvasRenderer__prototype = CanvasRenderer.prototype;
-
-/**
- * Fills the background with the specified color.
- * @param {string} fillColor Fill color on the format #rrggbb[aa].
- */
-CanvasRenderer__prototype.m/*setBackground*/ = function setBackground (fillColor) {
- var ctx = this.l/*_ctx*/;
- var iconSize = this.k/*iconSize*/;
-
- ctx.fillStyle = toCss3Color(fillColor);
- ctx.fillRect(0, 0, iconSize, iconSize);
-};
-
-/**
- * Marks the beginning of a new shape of the specified color. Should be ended with a call to endShape.
- * @param {string} fillColor Fill color on format #rrggbb[aa].
- */
-CanvasRenderer__prototype.O/*beginShape*/ = function beginShape (fillColor) {
- var ctx = this.l/*_ctx*/;
- ctx.fillStyle = toCss3Color(fillColor);
- ctx.beginPath();
-};
-
-/**
- * Marks the end of the currently drawn shape. This causes the queued paths to be rendered on the canvas.
- */
-CanvasRenderer__prototype.P/*endShape*/ = function endShape () {
- this.l/*_ctx*/.fill();
-};
-
-/**
- * Adds a polygon to the rendering queue.
- * @param points An array of Point objects.
- */
-CanvasRenderer__prototype.g/*addPolygon*/ = function addPolygon (points) {
- var ctx = this.l/*_ctx*/;
- ctx.moveTo(points[0].x, points[0].y);
- for (var i = 1; i < points.length; i++) {
- ctx.lineTo(points[i].x, points[i].y);
- }
- ctx.closePath();
-};
-
-/**
- * Adds a circle to the rendering queue.
- * @param {Point} point The upper left corner of the circle bounding box.
- * @param {number} diameter The diameter of the circle.
- * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path).
- */
-CanvasRenderer__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) {
- var ctx = this.l/*_ctx*/,
- radius = diameter / 2;
- ctx.moveTo(point.x + radius, point.y + radius);
- ctx.arc(point.x + radius, point.y + radius, radius, 0, Math.PI * 2, counterClockwise);
- ctx.closePath();
-};
-
-/**
- * Called when the icon has been completely drawn.
- */
-CanvasRenderer__prototype.finish = function finish () {
- this.l/*_ctx*/.restore();
-};
-
-/**
- * Draws an identicon to a context.
- * @param {CanvasRenderingContext2D} ctx - Canvas context on which the icon will be drawn at location (0, 0).
- * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon.
- * @param {number} size - Icon size in pixels.
- * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any
- * global configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be
- * specified in place of a configuration object.
- */
-function drawIcon(ctx, hashOrValue, size, config) {
- if (!ctx) {
- throw new Error("No canvas specified.");
- }
-
- iconGenerator(new CanvasRenderer(ctx, size),
- isValidHash(hashOrValue) || computeHash(hashOrValue),
- config);
-}
-
-/**
- * Prepares a measure to be used as a measure in an SVG path, by
- * rounding the measure to a single decimal. This reduces the file
- * size of the generated SVG with more than 50% in some cases.
- */
-function svgValue(value) {
- return ((value * 10 + 0.5) | 0) / 10;
-}
-
-/**
- * Represents an SVG path element.
- */
-function SvgPath() {
- /**
- * This property holds the data string (path.d) of the SVG path.
- * @type {string}
- */
- this.B/*dataString*/ = "";
-}
-var SvgPath__prototype = SvgPath.prototype;
-
-/**
- * Adds a polygon with the current fill color to the SVG path.
- * @param points An array of Point objects.
- */
-SvgPath__prototype.g/*addPolygon*/ = function addPolygon (points) {
- var dataString = "";
- for (var i = 0; i < points.length; i++) {
- dataString += (i ? "L" : "M") + svgValue(points[i].x) + " " + svgValue(points[i].y);
- }
- this.B/*dataString*/ += dataString + "Z";
-};
-
-/**
- * Adds a circle with the current fill color to the SVG path.
- * @param {Point} point The upper left corner of the circle bounding box.
- * @param {number} diameter The diameter of the circle.
- * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path).
- */
-SvgPath__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) {
- var sweepFlag = counterClockwise ? 0 : 1,
- svgRadius = svgValue(diameter / 2),
- svgDiameter = svgValue(diameter),
- svgArc = "a" + svgRadius + "," + svgRadius + " 0 1," + sweepFlag + " ";
-
- this.B/*dataString*/ +=
- "M" + svgValue(point.x) + " " + svgValue(point.y + diameter / 2) +
- svgArc + svgDiameter + ",0" +
- svgArc + (-svgDiameter) + ",0";
-};
-
-
-
-/**
- * Renderer producing SVG output.
- * @implements {Renderer}
- */
-function SvgRenderer(target) {
- /**
- * @type {SvgPath}
- * @private
- */
- this.C/*_path*/;
-
- /**
- * @type {Object.<string,SvgPath>}
- * @private
- */
- this.D/*_pathsByColor*/ = { };
-
- /**
- * @type {SvgElement|SvgWriter}
- * @private
- */
- this.R/*_target*/ = target;
-
- /**
- * @type {number}
- */
- this.k/*iconSize*/ = target.k/*iconSize*/;
-}
-var SvgRenderer__prototype = SvgRenderer.prototype;
-
-/**
- * Fills the background with the specified color.
- * @param {string} fillColor Fill color on the format #rrggbb[aa].
- */
-SvgRenderer__prototype.m/*setBackground*/ = function setBackground (fillColor) {
- var match = /^(#......)(..)?/.exec(fillColor),
- opacity = match[2] ? parseHex(match[2], 0) / 255 : 1;
- this.R/*_target*/.m/*setBackground*/(match[1], opacity);
-};
-
-/**
- * Marks the beginning of a new shape of the specified color. Should be ended with a call to endShape.
- * @param {string} color Fill color on format #xxxxxx.
- */
-SvgRenderer__prototype.O/*beginShape*/ = function beginShape (color) {
- this.C/*_path*/ = this.D/*_pathsByColor*/[color] || (this.D/*_pathsByColor*/[color] = new SvgPath());
-};
-
-/**
- * Marks the end of the currently drawn shape.
- */
-SvgRenderer__prototype.P/*endShape*/ = function endShape () { };
-
-/**
- * Adds a polygon with the current fill color to the SVG.
- * @param points An array of Point objects.
- */
-SvgRenderer__prototype.g/*addPolygon*/ = function addPolygon (points) {
- this.C/*_path*/.g/*addPolygon*/(points);
-};
-
-/**
- * Adds a circle with the current fill color to the SVG.
- * @param {Point} point The upper left corner of the circle bounding box.
- * @param {number} diameter The diameter of the circle.
- * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path).
- */
-SvgRenderer__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) {
- this.C/*_path*/.h/*addCircle*/(point, diameter, counterClockwise);
-};
-
-/**
- * Called when the icon has been completely drawn.
- */
-SvgRenderer__prototype.finish = function finish () {
- var this$1 = this;
-
- var pathsByColor = this.D/*_pathsByColor*/;
- for (var color in pathsByColor) {
- // hasOwnProperty cannot be shadowed in pathsByColor
- // eslint-disable-next-line no-prototype-builtins
- if (pathsByColor.hasOwnProperty(color)) {
- this$1.R/*_target*/.S/*appendPath*/(color, pathsByColor[color].B/*dataString*/);
- }
- }
-};
-
-var SVG_CONSTANTS = {
- T/*XMLNS*/: "http://www.w3.org/2000/svg",
- U/*WIDTH*/: "width",
- V/*HEIGHT*/: "height",
-};
-
-/**
- * Renderer producing SVG output.
- */
-function SvgWriter(iconSize) {
- /**
- * @type {number}
- */
- this.k/*iconSize*/ = iconSize;
-
- /**
- * @type {string}
- * @private
- */
- this.F/*_s*/ =
- '<svg xmlns="' + SVG_CONSTANTS.T/*XMLNS*/ + '" width="' +
- iconSize + '" height="' + iconSize + '" viewBox="0 0 ' +
- iconSize + ' ' + iconSize + '">';
-}
-var SvgWriter__prototype = SvgWriter.prototype;
-
-/**
- * Fills the background with the specified color.
- * @param {string} fillColor Fill color on the format #rrggbb.
- * @param {number} opacity Opacity in the range [0.0, 1.0].
- */
-SvgWriter__prototype.m/*setBackground*/ = function setBackground (fillColor, opacity) {
- if (opacity) {
- this.F/*_s*/ += '<rect width="100%" height="100%" fill="' +
- fillColor + '" opacity="' + opacity.toFixed(2) + '"/>';
- }
-};
-
-/**
- * Writes a path to the SVG string.
- * @param {string} color Fill color on format #rrggbb.
- * @param {string} dataString The SVG path data string.
- */
-SvgWriter__prototype.S/*appendPath*/ = function appendPath (color, dataString) {
- this.F/*_s*/ += '<path fill="' + color + '" d="' + dataString + '"/>';
-};
-
-/**
- * Gets the rendered image as an SVG string.
- */
-SvgWriter__prototype.toString = function toString () {
- return this.F/*_s*/ + "</svg>";
-};
-
-/**
- * Draws an identicon as an SVG string.
- * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon.
- * @param {number} size - Icon size in pixels.
- * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any
- * global configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be
- * specified in place of a configuration object.
- * @returns {string} SVG string
- */
-function toSvg(hashOrValue, size, config) {
- var writer = new SvgWriter(size);
- iconGenerator(new SvgRenderer(writer),
- isValidHash(hashOrValue) || computeHash(hashOrValue),
- config);
- return writer.toString();
-}
-
-/**
- * Creates a new element and adds it to the specified parent.
- * @param {Element} parentNode
- * @param {string} name
- * @param {...(string|number)} keyValuePairs
- */
-function SvgElement_append(parentNode, name) {
- var keyValuePairs = [], len = arguments.length - 2;
- while ( len-- > 0 ) keyValuePairs[ len ] = arguments[ len + 2 ];
-
- var el = document.createElementNS(SVG_CONSTANTS.T/*XMLNS*/, name);
-
- for (var i = 0; i + 1 < keyValuePairs.length; i += 2) {
- el.setAttribute(
- /** @type {string} */(keyValuePairs[i]),
- /** @type {string} */(keyValuePairs[i + 1])
- );
- }
-
- parentNode.appendChild(el);
-}
-
-
-/**
- * Renderer producing SVG output.
- */
-function SvgElement(element) {
- // Don't use the clientWidth and clientHeight properties on SVG elements
- // since Firefox won't serve a proper value of these properties on SVG
- // elements (https://bugzilla.mozilla.org/show_bug.cgi?id=874811)
- // Instead use 100px as a hardcoded size (the svg viewBox will rescale
- // the icon to the correct dimensions)
- var iconSize = this.k/*iconSize*/ = Math.min(
- (Number(element.getAttribute(SVG_CONSTANTS.U/*WIDTH*/)) || 100),
- (Number(element.getAttribute(SVG_CONSTANTS.V/*HEIGHT*/)) || 100)
- );
-
- /**
- * @type {Element}
- * @private
- */
- this.W/*_el*/ = element;
-
- // Clear current SVG child elements
- while (element.firstChild) {
- element.removeChild(element.firstChild);
- }
-
- // Set viewBox attribute to ensure the svg scales nicely.
- element.setAttribute("viewBox", "0 0 " + iconSize + " " + iconSize);
- element.setAttribute("preserveAspectRatio", "xMidYMid meet");
-}
-var SvgElement__prototype = SvgElement.prototype;
-
-/**
- * Fills the background with the specified color.
- * @param {string} fillColor Fill color on the format #rrggbb.
- * @param {number} opacity Opacity in the range [0.0, 1.0].
- */
-SvgElement__prototype.m/*setBackground*/ = function setBackground (fillColor, opacity) {
- if (opacity) {
- SvgElement_append(this.W/*_el*/, "rect",
- SVG_CONSTANTS.U/*WIDTH*/, "100%",
- SVG_CONSTANTS.V/*HEIGHT*/, "100%",
- "fill", fillColor,
- "opacity", opacity);
- }
-};
-
-/**
- * Appends a path to the SVG element.
- * @param {string} color Fill color on format #xxxxxx.
- * @param {string} dataString The SVG path data string.
- */
-SvgElement__prototype.S/*appendPath*/ = function appendPath (color, dataString) {
- SvgElement_append(this.W/*_el*/, "path",
- "fill", color,
- "d", dataString);
-};
-
-/**
- * Updates all canvas elements with the `data-jdenticon-hash` or `data-jdenticon-value` attribute.
- */
-function updateAll() {
- if (documentQuerySelectorAll) {
- update(ICON_SELECTOR);
- }
-}
-
-/**
- * Updates the identicon in the specified `<canvas>` or `<svg>` elements.
- * @param {(string|Element)} el - Specifies the container in which the icon is rendered as a DOM element of the type
- * `<svg>` or `<canvas>`, or a CSS selector to such an element.
- * @param {*=} hashOrValue - Optional hash or value to be rendered. If not specified, the `data-jdenticon-hash` or
- * `data-jdenticon-value` attribute will be evaluated.
- * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any
- * global configuration in its entirety. For backward compability a padding value in the range [0.0, 0.5) can be
- * specified in place of a configuration object.
- */
-function update(el, hashOrValue, config) {
- renderDomElement(el, hashOrValue, config, function (el, iconType) {
- if (iconType) {
- return iconType == ICON_TYPE_SVG ?
- new SvgRenderer(new SvgElement(el)) :
- new CanvasRenderer(/** @type {HTMLCanvasElement} */(el).getContext("2d"));
- }
- });
-}
-
-/**
- * Updates the identicon in the specified canvas or svg elements.
- * @param {(string|Element)} el - Specifies the container in which the icon is rendered as a DOM element of the type
- * `<svg>` or `<canvas>`, or a CSS selector to such an element.
- * @param {*} hashOrValue - Optional hash or value to be rendered. If not specified, the `data-jdenticon-hash` or
- * `data-jdenticon-value` attribute will be evaluated.
- * @param {Object|number|undefined} config
- * @param {function(Element,number):Renderer} rendererFactory - Factory function for creating an icon renderer.
- */
-function renderDomElement(el, hashOrValue, config, rendererFactory) {
- if (typeof el === "string") {
- if (documentQuerySelectorAll) {
- var elements = documentQuerySelectorAll(el);
- for (var i = 0; i < elements.length; i++) {
- renderDomElement(elements[i], hashOrValue, config, rendererFactory);
- }
- }
- return;
- }
-
- // Hash selection. The result from getValidHash or computeHash is
- // accepted as a valid hash.
- var hash =
- // 1. Explicit valid hash
- isValidHash(hashOrValue) ||
-
- // 2. Explicit value (`!= null` catches both null and undefined)
- hashOrValue != null && computeHash(hashOrValue) ||
-
- // 3. `data-jdenticon-hash` attribute
- isValidHash(el.getAttribute(ATTRIBUTES.t/*HASH*/)) ||
-
- // 4. `data-jdenticon-value` attribute.
- // We want to treat an empty attribute as an empty value.
- // Some browsers return empty string even if the attribute
- // is not specified, so use hasAttribute to determine if
- // the attribute is specified.
- el.hasAttribute(ATTRIBUTES.o/*VALUE*/) && computeHash(el.getAttribute(ATTRIBUTES.o/*VALUE*/));
-
- if (!hash) {
- // No hash specified. Don't render an icon.
- return;
- }
-
- var renderer = rendererFactory(el, getIdenticonType(el));
- if (renderer) {
- // Draw icon
- iconGenerator(renderer, hash, config);
- }
-}
-
-/**
- * Renders an identicon for all matching supported elements.
- *
- * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon. If not
- * specified the `data-jdenticon-hash` and `data-jdenticon-value` attributes of each element will be
- * evaluated.
- * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any global
- * configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be
- * specified in place of a configuration object.
- */
-function jdenticonJqueryPlugin(hashOrValue, config) {
- this["each"](function (index, el) {
- update(el, hashOrValue, config);
- });
- return this;
-}
-
-// This file is compiled to dist/jdenticon.js and dist/jdenticon.min.js
-
-var jdenticon = updateAll;
-
-defineConfigProperty(jdenticon);
-
-// Export public API
-jdenticon["configure"] = configure;
-jdenticon["drawIcon"] = drawIcon;
-jdenticon["toSvg"] = toSvg;
-jdenticon["update"] = update;
-jdenticon["updateCanvas"] = update;
-jdenticon["updateSvg"] = update;
-
-/**
- * Specifies the version of the Jdenticon package in use.
- * @type {string}
- */
-jdenticon["version"] = "3.2.0";
-
-/**
- * Specifies which bundle of Jdenticon that is used.
- * @type {string}
- */
-jdenticon["bundle"] = "browser-umd";
-
-// Basic jQuery plugin
-var jQuery = GLOBAL["jQuery"];
-if (jQuery) {
- jQuery["fn"]["jdenticon"] = jdenticonJqueryPlugin;
-}
-
-/**
- * This function is called once upon page load.
- */
-function jdenticonStartup() {
- var replaceMode = (
- jdenticon[CONFIG_PROPERTIES.n/*MODULE*/] ||
- GLOBAL[CONFIG_PROPERTIES.G/*GLOBAL*/] ||
- { }
- )["replaceMode"];
-
- if (replaceMode != "never") {
- updateAll();
-
- if (replaceMode == "observe") {
- observer(update);
- }
- }
-}
-
-// Schedule to render all identicons on the page once it has been loaded.
-if (typeof setTimeout === "function") {
- setTimeout(jdenticonStartup, 0);
-}
-
-return jdenticon;
-
-});
-\ No newline at end of file
diff --git a/src/static/scripts/jquery-3.7.0.slim.js b/src/static/scripts/jquery-3.7.0.slim.js
@@ -1,8605 +0,0 @@
-/*!
- * jQuery JavaScript Library v3.7.0 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/animatedSelector,-effects/Tween
- * https://jquery.com/
- *
- * Copyright OpenJS Foundation and other contributors
- * Released under the MIT license
- * https://jquery.org/license
- *
- * Date: 2023-05-11T18:29Z
- */
-( function( global, factory ) {
-
- "use strict";
-
- if ( typeof module === "object" && typeof module.exports === "object" ) {
-
- // For CommonJS and CommonJS-like environments where a proper `window`
- // is present, execute the factory and get jQuery.
- // For environments that do not have a `window` with a `document`
- // (such as Node.js), expose a factory as module.exports.
- // This accentuates the need for the creation of a real `window`.
- // e.g. var jQuery = require("jquery")(window);
- // See ticket trac-14549 for more info.
- module.exports = global.document ?
- factory( global, true ) :
- function( w ) {
- if ( !w.document ) {
- throw new Error( "jQuery requires a window with a document" );
- }
- return factory( w );
- };
- } else {
- factory( global );
- }
-
-// Pass this if window is not defined yet
-} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
-
-// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
-// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
-// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
-// enough that all such attempts are guarded in a try block.
-"use strict";
-
-var arr = [];
-
-var getProto = Object.getPrototypeOf;
-
-var slice = arr.slice;
-
-var flat = arr.flat ? function( array ) {
- return arr.flat.call( array );
-} : function( array ) {
- return arr.concat.apply( [], array );
-};
-
-
-var push = arr.push;
-
-var indexOf = arr.indexOf;
-
-var class2type = {};
-
-var toString = class2type.toString;
-
-var hasOwn = class2type.hasOwnProperty;
-
-var fnToString = hasOwn.toString;
-
-var ObjectFunctionString = fnToString.call( Object );
-
-var support = {};
-
-var isFunction = function isFunction( obj ) {
-
- // Support: Chrome <=57, Firefox <=52
- // In some browsers, typeof returns "function" for HTML <object> elements
- // (i.e., `typeof document.createElement( "object" ) === "function"`).
- // We don't want to classify *any* DOM node as a function.
- // Support: QtWeb <=3.8.5, WebKit <=534.34, wkhtmltopdf tool <=0.12.5
- // Plus for old WebKit, typeof returns "function" for HTML collections
- // (e.g., `typeof document.getElementsByTagName("div") === "function"`). (gh-4756)
- return typeof obj === "function" && typeof obj.nodeType !== "number" &&
- typeof obj.item !== "function";
- };
-
-
-var isWindow = function isWindow( obj ) {
- return obj != null && obj === obj.window;
- };
-
-
-var document = window.document;
-
-
-
- var preservedScriptAttributes = {
- type: true,
- src: true,
- nonce: true,
- noModule: true
- };
-
- function DOMEval( code, node, doc ) {
- doc = doc || document;
-
- var i, val,
- script = doc.createElement( "script" );
-
- script.text = code;
- if ( node ) {
- for ( i in preservedScriptAttributes ) {
-
- // Support: Firefox 64+, Edge 18+
- // Some browsers don't support the "nonce" property on scripts.
- // On the other hand, just using `getAttribute` is not enough as
- // the `nonce` attribute is reset to an empty string whenever it
- // becomes browsing-context connected.
- // See https://github.com/whatwg/html/issues/2369
- // See https://html.spec.whatwg.org/#nonce-attributes
- // The `node.getAttribute` check was added for the sake of
- // `jQuery.globalEval` so that it can fake a nonce-containing node
- // via an object.
- val = node[ i ] || node.getAttribute && node.getAttribute( i );
- if ( val ) {
- script.setAttribute( i, val );
- }
- }
- }
- doc.head.appendChild( script ).parentNode.removeChild( script );
- }
-
-
-function toType( obj ) {
- if ( obj == null ) {
- return obj + "";
- }
-
- // Support: Android <=2.3 only (functionish RegExp)
- return typeof obj === "object" || typeof obj === "function" ?
- class2type[ toString.call( obj ) ] || "object" :
- typeof obj;
-}
-/* global Symbol */
-// Defining this global in .eslintrc.json would create a danger of using the global
-// unguarded in another place, it seems safer to define global only for this module
-
-
-
-var version = "3.7.0 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/animatedSelector,-effects/Tween",
-
- rhtmlSuffix = /HTML$/i,
-
- // Define a local copy of jQuery
- jQuery = function( selector, context ) {
-
- // The jQuery object is actually just the init constructor 'enhanced'
- // Need init if jQuery is called (just allow error to be thrown if not included)
- return new jQuery.fn.init( selector, context );
- };
-
-jQuery.fn = jQuery.prototype = {
-
- // The current version of jQuery being used
- jquery: version,
-
- constructor: jQuery,
-
- // The default length of a jQuery object is 0
- length: 0,
-
- toArray: function() {
- return slice.call( this );
- },
-
- // Get the Nth element in the matched element set OR
- // Get the whole matched element set as a clean array
- get: function( num ) {
-
- // Return all the elements in a clean array
- if ( num == null ) {
- return slice.call( this );
- }
-
- // Return just the one element from the set
- return num < 0 ? this[ num + this.length ] : this[ num ];
- },
-
- // Take an array of elements and push it onto the stack
- // (returning the new matched element set)
- pushStack: function( elems ) {
-
- // Build a new jQuery matched element set
- var ret = jQuery.merge( this.constructor(), elems );
-
- // Add the old object onto the stack (as a reference)
- ret.prevObject = this;
-
- // Return the newly-formed element set
- return ret;
- },
-
- // Execute a callback for every element in the matched set.
- each: function( callback ) {
- return jQuery.each( this, callback );
- },
-
- map: function( callback ) {
- return this.pushStack( jQuery.map( this, function( elem, i ) {
- return callback.call( elem, i, elem );
- } ) );
- },
-
- slice: function() {
- return this.pushStack( slice.apply( this, arguments ) );
- },
-
- first: function() {
- return this.eq( 0 );
- },
-
- last: function() {
- return this.eq( -1 );
- },
-
- even: function() {
- return this.pushStack( jQuery.grep( this, function( _elem, i ) {
- return ( i + 1 ) % 2;
- } ) );
- },
-
- odd: function() {
- return this.pushStack( jQuery.grep( this, function( _elem, i ) {
- return i % 2;
- } ) );
- },
-
- eq: function( i ) {
- var len = this.length,
- j = +i + ( i < 0 ? len : 0 );
- return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
- },
-
- end: function() {
- return this.prevObject || this.constructor();
- },
-
- // For internal use only.
- // Behaves like an Array's method, not like a jQuery method.
- push: push,
- sort: arr.sort,
- splice: arr.splice
-};
-
-jQuery.extend = jQuery.fn.extend = function() {
- var options, name, src, copy, copyIsArray, clone,
- target = arguments[ 0 ] || {},
- i = 1,
- length = arguments.length,
- deep = false;
-
- // Handle a deep copy situation
- if ( typeof target === "boolean" ) {
- deep = target;
-
- // Skip the boolean and the target
- target = arguments[ i ] || {};
- i++;
- }
-
- // Handle case when target is a string or something (possible in deep copy)
- if ( typeof target !== "object" && !isFunction( target ) ) {
- target = {};
- }
-
- // Extend jQuery itself if only one argument is passed
- if ( i === length ) {
- target = this;
- i--;
- }
-
- for ( ; i < length; i++ ) {
-
- // Only deal with non-null/undefined values
- if ( ( options = arguments[ i ] ) != null ) {
-
- // Extend the base object
- for ( name in options ) {
- copy = options[ name ];
-
- // Prevent Object.prototype pollution
- // Prevent never-ending loop
- if ( name === "__proto__" || target === copy ) {
- continue;
- }
-
- // Recurse if we're merging plain objects or arrays
- if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
- ( copyIsArray = Array.isArray( copy ) ) ) ) {
- src = target[ name ];
-
- // Ensure proper type for the source value
- if ( copyIsArray && !Array.isArray( src ) ) {
- clone = [];
- } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) {
- clone = {};
- } else {
- clone = src;
- }
- copyIsArray = false;
-
- // Never move original objects, clone them
- target[ name ] = jQuery.extend( deep, clone, copy );
-
- // Don't bring in undefined values
- } else if ( copy !== undefined ) {
- target[ name ] = copy;
- }
- }
- }
- }
-
- // Return the modified object
- return target;
-};
-
-jQuery.extend( {
-
- // Unique for each copy of jQuery on the page
- expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
-
- // Assume jQuery is ready without the ready module
- isReady: true,
-
- error: function( msg ) {
- throw new Error( msg );
- },
-
- noop: function() {},
-
- isPlainObject: function( obj ) {
- var proto, Ctor;
-
- // Detect obvious negatives
- // Use toString instead of jQuery.type to catch host objects
- if ( !obj || toString.call( obj ) !== "[object Object]" ) {
- return false;
- }
-
- proto = getProto( obj );
-
- // Objects with no prototype (e.g., `Object.create( null )`) are plain
- if ( !proto ) {
- return true;
- }
-
- // Objects with prototype are plain iff they were constructed by a global Object function
- Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
- return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
- },
-
- isEmptyObject: function( obj ) {
- var name;
-
- for ( name in obj ) {
- return false;
- }
- return true;
- },
-
- // Evaluates a script in a provided context; falls back to the global one
- // if not specified.
- globalEval: function( code, options, doc ) {
- DOMEval( code, { nonce: options && options.nonce }, doc );
- },
-
- each: function( obj, callback ) {
- var length, i = 0;
-
- if ( isArrayLike( obj ) ) {
- length = obj.length;
- for ( ; i < length; i++ ) {
- if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
- break;
- }
- }
- } else {
- for ( i in obj ) {
- if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
- break;
- }
- }
- }
-
- return obj;
- },
-
-
- // Retrieve the text value of an array of DOM nodes
- text: function( elem ) {
- var node,
- ret = "",
- i = 0,
- nodeType = elem.nodeType;
-
- if ( !nodeType ) {
-
- // If no nodeType, this is expected to be an array
- while ( ( node = elem[ i++ ] ) ) {
-
- // Do not traverse comment nodes
- ret += jQuery.text( node );
- }
- } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
- return elem.textContent;
- } else if ( nodeType === 3 || nodeType === 4 ) {
- return elem.nodeValue;
- }
-
- // Do not include comment or processing instruction nodes
-
- return ret;
- },
-
- // results is for internal usage only
- makeArray: function( arr, results ) {
- var ret = results || [];
-
- if ( arr != null ) {
- if ( isArrayLike( Object( arr ) ) ) {
- jQuery.merge( ret,
- typeof arr === "string" ?
- [ arr ] : arr
- );
- } else {
- push.call( ret, arr );
- }
- }
-
- return ret;
- },
-
- inArray: function( elem, arr, i ) {
- return arr == null ? -1 : indexOf.call( arr, elem, i );
- },
-
- isXMLDoc: function( elem ) {
- var namespace = elem && elem.namespaceURI,
- docElem = elem && ( elem.ownerDocument || elem ).documentElement;
-
- // Assume HTML when documentElement doesn't yet exist, such as inside
- // document fragments.
- return !rhtmlSuffix.test( namespace || docElem && docElem.nodeName || "HTML" );
- },
-
- // Support: Android <=4.0 only, PhantomJS 1 only
- // push.apply(_, arraylike) throws on ancient WebKit
- merge: function( first, second ) {
- var len = +second.length,
- j = 0,
- i = first.length;
-
- for ( ; j < len; j++ ) {
- first[ i++ ] = second[ j ];
- }
-
- first.length = i;
-
- return first;
- },
-
- grep: function( elems, callback, invert ) {
- var callbackInverse,
- matches = [],
- i = 0,
- length = elems.length,
- callbackExpect = !invert;
-
- // Go through the array, only saving the items
- // that pass the validator function
- for ( ; i < length; i++ ) {
- callbackInverse = !callback( elems[ i ], i );
- if ( callbackInverse !== callbackExpect ) {
- matches.push( elems[ i ] );
- }
- }
-
- return matches;
- },
-
- // arg is for internal usage only
- map: function( elems, callback, arg ) {
- var length, value,
- i = 0,
- ret = [];
-
- // Go through the array, translating each of the items to their new values
- if ( isArrayLike( elems ) ) {
- length = elems.length;
- for ( ; i < length; i++ ) {
- value = callback( elems[ i ], i, arg );
-
- if ( value != null ) {
- ret.push( value );
- }
- }
-
- // Go through every key on the object,
- } else {
- for ( i in elems ) {
- value = callback( elems[ i ], i, arg );
-
- if ( value != null ) {
- ret.push( value );
- }
- }
- }
-
- // Flatten any nested arrays
- return flat( ret );
- },
-
- // A global GUID counter for objects
- guid: 1,
-
- // jQuery.support is not used in Core but other projects attach their
- // properties to it so it needs to exist.
- support: support
-} );
-
-if ( typeof Symbol === "function" ) {
- jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ];
-}
-
-// Populate the class2type map
-jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
- function( _i, name ) {
- class2type[ "[object " + name + "]" ] = name.toLowerCase();
- } );
-
-function isArrayLike( obj ) {
-
- // Support: real iOS 8.2 only (not reproducible in simulator)
- // `in` check used to prevent JIT error (gh-2145)
- // hasOwn isn't used here due to false negatives
- // regarding Nodelist length in IE
- var length = !!obj && "length" in obj && obj.length,
- type = toType( obj );
-
- if ( isFunction( obj ) || isWindow( obj ) ) {
- return false;
- }
-
- return type === "array" || length === 0 ||
- typeof length === "number" && length > 0 && ( length - 1 ) in obj;
-}
-
-
-function nodeName( elem, name ) {
-
- return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
-
-}
-var pop = arr.pop;
-
-
-var sort = arr.sort;
-
-
-var splice = arr.splice;
-
-
-var whitespace = "[\\x20\\t\\r\\n\\f]";
-
-
-var rtrimCSS = new RegExp(
- "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$",
- "g"
-);
-
-
-
-
-// Note: an element does not contain itself
-jQuery.contains = function( a, b ) {
- var bup = b && b.parentNode;
-
- return a === bup || !!( bup && bup.nodeType === 1 && (
-
- // Support: IE 9 - 11+
- // IE doesn't have `contains` on SVG.
- a.contains ?
- a.contains( bup ) :
- a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
- ) );
-};
-
-
-
-
-// CSS string/identifier serialization
-// https://drafts.csswg.org/cssom/#common-serializing-idioms
-var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g;
-
-function fcssescape( ch, asCodePoint ) {
- if ( asCodePoint ) {
-
- // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
- if ( ch === "\0" ) {
- return "\uFFFD";
- }
-
- // Control characters and (dependent upon position) numbers get escaped as code points
- return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " ";
- }
-
- // Other potentially-special ASCII characters get backslash-escaped
- return "\\" + ch;
-}
-
-jQuery.escapeSelector = function( sel ) {
- return ( sel + "" ).replace( rcssescape, fcssescape );
-};
-
-
-
-
-var preferredDoc = document,
- pushNative = push;
-
-( function() {
-
-var i,
- Expr,
- outermostContext,
- sortInput,
- hasDuplicate,
- push = pushNative,
-
- // Local document vars
- document,
- documentElement,
- documentIsHTML,
- rbuggyQSA,
- matches,
-
- // Instance-specific data
- expando = jQuery.expando,
- dirruns = 0,
- done = 0,
- classCache = createCache(),
- tokenCache = createCache(),
- compilerCache = createCache(),
- nonnativeSelectorCache = createCache(),
- sortOrder = function( a, b ) {
- if ( a === b ) {
- hasDuplicate = true;
- }
- return 0;
- },
-
- booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|" +
- "loop|multiple|open|readonly|required|scoped",
-
- // Regular expressions
-
- // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
- identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace +
- "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",
-
- // Attribute selectors: https://www.w3.org/TR/selectors/#attribute-selectors
- attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
-
- // Operator (capture 2)
- "*([*^$|!~]?=)" + whitespace +
-
- // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
- "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" +
- whitespace + "*\\]",
-
- pseudos = ":(" + identifier + ")(?:\\((" +
-
- // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
- // 1. quoted (capture 3; capture 4 or capture 5)
- "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
-
- // 2. simple (capture 6)
- "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
-
- // 3. anything else (capture 2)
- ".*" +
- ")\\)|)",
-
- // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
- rwhitespace = new RegExp( whitespace + "+", "g" ),
-
- rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
- rleadingCombinator = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" +
- whitespace + "*" ),
- rdescend = new RegExp( whitespace + "|>" ),
-
- rpseudo = new RegExp( pseudos ),
- ridentifier = new RegExp( "^" + identifier + "$" ),
-
- matchExpr = {
- ID: new RegExp( "^#(" + identifier + ")" ),
- CLASS: new RegExp( "^\\.(" + identifier + ")" ),
- TAG: new RegExp( "^(" + identifier + "|[*])" ),
- ATTR: new RegExp( "^" + attributes ),
- PSEUDO: new RegExp( "^" + pseudos ),
- CHILD: new RegExp(
- "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" +
- whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" +
- whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
- bool: new RegExp( "^(?:" + booleans + ")$", "i" ),
-
- // For use in libraries implementing .is()
- // We use this for POS matching in `select`
- needsContext: new RegExp( "^" + whitespace +
- "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace +
- "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
- },
-
- rinputs = /^(?:input|select|textarea|button)$/i,
- rheader = /^h\d$/i,
-
- // Easily-parseable/retrievable ID or TAG or CLASS selectors
- rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
-
- rsibling = /[+~]/,
-
- // CSS escapes
- // https://www.w3.org/TR/CSS21/syndata.html#escaped-characters
- runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace +
- "?|\\\\([^\\r\\n\\f])", "g" ),
- funescape = function( escape, nonHex ) {
- var high = "0x" + escape.slice( 1 ) - 0x10000;
-
- if ( nonHex ) {
-
- // Strip the backslash prefix from a non-hex escape sequence
- return nonHex;
- }
-
- // Replace a hexadecimal escape sequence with the encoded Unicode code point
- // Support: IE <=11+
- // For values outside the Basic Multilingual Plane (BMP), manually construct a
- // surrogate pair
- return high < 0 ?
- String.fromCharCode( high + 0x10000 ) :
- String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
- },
-
- // Used for iframes; see `setDocument`.
- // Support: IE 9 - 11+, Edge 12 - 18+
- // Removing the function wrapper causes a "Permission Denied"
- // error in IE/Edge.
- unloadHandler = function() {
- setDocument();
- },
-
- inDisabledFieldset = addCombinator(
- function( elem ) {
- return elem.disabled === true && nodeName( elem, "fieldset" );
- },
- { dir: "parentNode", next: "legend" }
- );
-
-// Support: IE <=9 only
-// Accessing document.activeElement can throw unexpectedly
-// https://bugs.jquery.com/ticket/13393
-function safeActiveElement() {
- try {
- return document.activeElement;
- } catch ( err ) { }
-}
-
-// Optimize for push.apply( _, NodeList )
-try {
- push.apply(
- ( arr = slice.call( preferredDoc.childNodes ) ),
- preferredDoc.childNodes
- );
-
- // Support: Android <=4.0
- // Detect silently failing push.apply
- // eslint-disable-next-line no-unused-expressions
- arr[ preferredDoc.childNodes.length ].nodeType;
-} catch ( e ) {
- push = {
- apply: function( target, els ) {
- pushNative.apply( target, slice.call( els ) );
- },
- call: function( target ) {
- pushNative.apply( target, slice.call( arguments, 1 ) );
- }
- };
-}
-
-function find( selector, context, results, seed ) {
- var m, i, elem, nid, match, groups, newSelector,
- newContext = context && context.ownerDocument,
-
- // nodeType defaults to 9, since context defaults to document
- nodeType = context ? context.nodeType : 9;
-
- results = results || [];
-
- // Return early from calls with invalid selector or context
- if ( typeof selector !== "string" || !selector ||
- nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
-
- return results;
- }
-
- // Try to shortcut find operations (as opposed to filters) in HTML documents
- if ( !seed ) {
- setDocument( context );
- context = context || document;
-
- if ( documentIsHTML ) {
-
- // If the selector is sufficiently simple, try using a "get*By*" DOM method
- // (excepting DocumentFragment context, where the methods don't exist)
- if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) {
-
- // ID selector
- if ( ( m = match[ 1 ] ) ) {
-
- // Document context
- if ( nodeType === 9 ) {
- if ( ( elem = context.getElementById( m ) ) ) {
-
- // Support: IE 9 only
- // getElementById can match elements by name instead of ID
- if ( elem.id === m ) {
- push.call( results, elem );
- return results;
- }
- } else {
- return results;
- }
-
- // Element context
- } else {
-
- // Support: IE 9 only
- // getElementById can match elements by name instead of ID
- if ( newContext && ( elem = newContext.getElementById( m ) ) &&
- find.contains( context, elem ) &&
- elem.id === m ) {
-
- push.call( results, elem );
- return results;
- }
- }
-
- // Type selector
- } else if ( match[ 2 ] ) {
- push.apply( results, context.getElementsByTagName( selector ) );
- return results;
-
- // Class selector
- } else if ( ( m = match[ 3 ] ) && context.getElementsByClassName ) {
- push.apply( results, context.getElementsByClassName( m ) );
- return results;
- }
- }
-
- // Take advantage of querySelectorAll
- if ( !nonnativeSelectorCache[ selector + " " ] &&
- ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) ) {
-
- newSelector = selector;
- newContext = context;
-
- // qSA considers elements outside a scoping root when evaluating child or
- // descendant combinators, which is not what we want.
- // In such cases, we work around the behavior by prefixing every selector in the
- // list with an ID selector referencing the scope context.
- // The technique has to be used as well when a leading combinator is used
- // as such selectors are not recognized by querySelectorAll.
- // Thanks to Andrew Dupont for this technique.
- if ( nodeType === 1 &&
- ( rdescend.test( selector ) || rleadingCombinator.test( selector ) ) ) {
-
- // Expand context for sibling selectors
- newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
- context;
-
- // We can use :scope instead of the ID hack if the browser
- // supports it & if we're not changing the context.
- // Support: IE 11+, Edge 17 - 18+
- // IE/Edge sometimes throw a "Permission denied" error when
- // strict-comparing two documents; shallow comparisons work.
- // eslint-disable-next-line eqeqeq
- if ( newContext != context || !support.scope ) {
-
- // Capture the context ID, setting it first if necessary
- if ( ( nid = context.getAttribute( "id" ) ) ) {
- nid = jQuery.escapeSelector( nid );
- } else {
- context.setAttribute( "id", ( nid = expando ) );
- }
- }
-
- // Prefix every selector in the list
- groups = tokenize( selector );
- i = groups.length;
- while ( i-- ) {
- groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " +
- toSelector( groups[ i ] );
- }
- newSelector = groups.join( "," );
- }
-
- try {
- push.apply( results,
- newContext.querySelectorAll( newSelector )
- );
- return results;
- } catch ( qsaError ) {
- nonnativeSelectorCache( selector, true );
- } finally {
- if ( nid === expando ) {
- context.removeAttribute( "id" );
- }
- }
- }
- }
- }
-
- // All others
- return select( selector.replace( rtrimCSS, "$1" ), context, results, seed );
-}
-
-/**
- * Create key-value caches of limited size
- * @returns {function(string, object)} Returns the Object data after storing it on itself with
- * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
- * deleting the oldest entry
- */
-function createCache() {
- var keys = [];
-
- function cache( key, value ) {
-
- // Use (key + " ") to avoid collision with native prototype properties
- // (see https://github.com/jquery/sizzle/issues/157)
- if ( keys.push( key + " " ) > Expr.cacheLength ) {
-
- // Only keep the most recent entries
- delete cache[ keys.shift() ];
- }
- return ( cache[ key + " " ] = value );
- }
- return cache;
-}
-
-/**
- * Mark a function for special use by jQuery selector module
- * @param {Function} fn The function to mark
- */
-function markFunction( fn ) {
- fn[ expando ] = true;
- return fn;
-}
-
-/**
- * Support testing using an element
- * @param {Function} fn Passed the created element and returns a boolean result
- */
-function assert( fn ) {
- var el = document.createElement( "fieldset" );
-
- try {
- return !!fn( el );
- } catch ( e ) {
- return false;
- } finally {
-
- // Remove from its parent by default
- if ( el.parentNode ) {
- el.parentNode.removeChild( el );
- }
-
- // release memory in IE
- el = null;
- }
-}
-
-/**
- * Returns a function to use in pseudos for input types
- * @param {String} type
- */
-function createInputPseudo( type ) {
- return function( elem ) {
- return nodeName( elem, "input" ) && elem.type === type;
- };
-}
-
-/**
- * Returns a function to use in pseudos for buttons
- * @param {String} type
- */
-function createButtonPseudo( type ) {
- return function( elem ) {
- return ( nodeName( elem, "input" ) || nodeName( elem, "button" ) ) &&
- elem.type === type;
- };
-}
-
-/**
- * Returns a function to use in pseudos for :enabled/:disabled
- * @param {Boolean} disabled true for :disabled; false for :enabled
- */
-function createDisabledPseudo( disabled ) {
-
- // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable
- return function( elem ) {
-
- // Only certain elements can match :enabled or :disabled
- // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled
- // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
- if ( "form" in elem ) {
-
- // Check for inherited disabledness on relevant non-disabled elements:
- // * listed form-associated elements in a disabled fieldset
- // https://html.spec.whatwg.org/multipage/forms.html#category-listed
- // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled
- // * option elements in a disabled optgroup
- // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled
- // All such elements have a "form" property.
- if ( elem.parentNode && elem.disabled === false ) {
-
- // Option elements defer to a parent optgroup if present
- if ( "label" in elem ) {
- if ( "label" in elem.parentNode ) {
- return elem.parentNode.disabled === disabled;
- } else {
- return elem.disabled === disabled;
- }
- }
-
- // Support: IE 6 - 11+
- // Use the isDisabled shortcut property to check for disabled fieldset ancestors
- return elem.isDisabled === disabled ||
-
- // Where there is no isDisabled, check manually
- elem.isDisabled !== !disabled &&
- inDisabledFieldset( elem ) === disabled;
- }
-
- return elem.disabled === disabled;
-
- // Try to winnow out elements that can't be disabled before trusting the disabled property.
- // Some victims get caught in our net (label, legend, menu, track), but it shouldn't
- // even exist on them, let alone have a boolean value.
- } else if ( "label" in elem ) {
- return elem.disabled === disabled;
- }
-
- // Remaining elements are neither :enabled nor :disabled
- return false;
- };
-}
-
-/**
- * Returns a function to use in pseudos for positionals
- * @param {Function} fn
- */
-function createPositionalPseudo( fn ) {
- return markFunction( function( argument ) {
- argument = +argument;
- return markFunction( function( seed, matches ) {
- var j,
- matchIndexes = fn( [], seed.length, argument ),
- i = matchIndexes.length;
-
- // Match elements found at the specified indexes
- while ( i-- ) {
- if ( seed[ ( j = matchIndexes[ i ] ) ] ) {
- seed[ j ] = !( matches[ j ] = seed[ j ] );
- }
- }
- } );
- } );
-}
-
-/**
- * Checks a node for validity as a jQuery selector context
- * @param {Element|Object=} context
- * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
- */
-function testContext( context ) {
- return context && typeof context.getElementsByTagName !== "undefined" && context;
-}
-
-/**
- * Sets document-related variables once based on the current document
- * @param {Element|Object} [node] An element or document object to use to set the document
- * @returns {Object} Returns the current document
- */
-function setDocument( node ) {
- var subWindow,
- doc = node ? node.ownerDocument || node : preferredDoc;
-
- // Return early if doc is invalid or already selected
- // Support: IE 11+, Edge 17 - 18+
- // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
- // two documents; shallow comparisons work.
- // eslint-disable-next-line eqeqeq
- if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) {
- return document;
- }
-
- // Update global variables
- document = doc;
- documentElement = document.documentElement;
- documentIsHTML = !jQuery.isXMLDoc( document );
-
- // Support: iOS 7 only, IE 9 - 11+
- // Older browsers didn't support unprefixed `matches`.
- matches = documentElement.matches ||
- documentElement.webkitMatchesSelector ||
- documentElement.msMatchesSelector;
-
- // Support: IE 9 - 11+, Edge 12 - 18+
- // Accessing iframe documents after unload throws "permission denied" errors (see trac-13936)
- // Support: IE 11+, Edge 17 - 18+
- // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
- // two documents; shallow comparisons work.
- // eslint-disable-next-line eqeqeq
- if ( preferredDoc != document &&
- ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) {
-
- // Support: IE 9 - 11+, Edge 12 - 18+
- subWindow.addEventListener( "unload", unloadHandler );
- }
-
- // Support: IE <10
- // Check if getElementById returns elements by name
- // The broken getElementById methods don't pick up programmatically-set names,
- // so use a roundabout getElementsByName test
- support.getById = assert( function( el ) {
- documentElement.appendChild( el ).id = jQuery.expando;
- return !document.getElementsByName ||
- !document.getElementsByName( jQuery.expando ).length;
- } );
-
- // Support: IE 9 only
- // Check to see if it's possible to do matchesSelector
- // on a disconnected node.
- support.disconnectedMatch = assert( function( el ) {
- return matches.call( el, "*" );
- } );
-
- // Support: IE 9 - 11+, Edge 12 - 18+
- // IE/Edge don't support the :scope pseudo-class.
- support.scope = assert( function() {
- return document.querySelectorAll( ":scope" );
- } );
-
- // Support: Chrome 105 - 111 only, Safari 15.4 - 16.3 only
- // Make sure the `:has()` argument is parsed unforgivingly.
- // We include `*` in the test to detect buggy implementations that are
- // _selectively_ forgiving (specifically when the list includes at least
- // one valid selector).
- // Note that we treat complete lack of support for `:has()` as if it were
- // spec-compliant support, which is fine because use of `:has()` in such
- // environments will fail in the qSA path and fall back to jQuery traversal
- // anyway.
- support.cssHas = assert( function() {
- try {
- document.querySelector( ":has(*,:jqfake)" );
- return false;
- } catch ( e ) {
- return true;
- }
- } );
-
- // ID filter and find
- if ( support.getById ) {
- Expr.filter.ID = function( id ) {
- var attrId = id.replace( runescape, funescape );
- return function( elem ) {
- return elem.getAttribute( "id" ) === attrId;
- };
- };
- Expr.find.ID = function( id, context ) {
- if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
- var elem = context.getElementById( id );
- return elem ? [ elem ] : [];
- }
- };
- } else {
- Expr.filter.ID = function( id ) {
- var attrId = id.replace( runescape, funescape );
- return function( elem ) {
- var node = typeof elem.getAttributeNode !== "undefined" &&
- elem.getAttributeNode( "id" );
- return node && node.value === attrId;
- };
- };
-
- // Support: IE 6 - 7 only
- // getElementById is not reliable as a find shortcut
- Expr.find.ID = function( id, context ) {
- if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
- var node, i, elems,
- elem = context.getElementById( id );
-
- if ( elem ) {
-
- // Verify the id attribute
- node = elem.getAttributeNode( "id" );
- if ( node && node.value === id ) {
- return [ elem ];
- }
-
- // Fall back on getElementsByName
- elems = context.getElementsByName( id );
- i = 0;
- while ( ( elem = elems[ i++ ] ) ) {
- node = elem.getAttributeNode( "id" );
- if ( node && node.value === id ) {
- return [ elem ];
- }
- }
- }
-
- return [];
- }
- };
- }
-
- // Tag
- Expr.find.TAG = function( tag, context ) {
- if ( typeof context.getElementsByTagName !== "undefined" ) {
- return context.getElementsByTagName( tag );
-
- // DocumentFragment nodes don't have gEBTN
- } else {
- return context.querySelectorAll( tag );
- }
- };
-
- // Class
- Expr.find.CLASS = function( className, context ) {
- if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) {
- return context.getElementsByClassName( className );
- }
- };
-
- /* QSA/matchesSelector
- ---------------------------------------------------------------------- */
-
- // QSA and matchesSelector support
-
- rbuggyQSA = [];
-
- // Build QSA regex
- // Regex strategy adopted from Diego Perini
- assert( function( el ) {
-
- var input;
-
- documentElement.appendChild( el ).innerHTML =
- "<a id='" + expando + "' href='' disabled='disabled'></a>" +
- "<select id='" + expando + "-\r\\' disabled='disabled'>" +
- "<option selected=''></option></select>";
-
- // Support: iOS <=7 - 8 only
- // Boolean attributes and "value" are not treated correctly in some XML documents
- if ( !el.querySelectorAll( "[selected]" ).length ) {
- rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
- }
-
- // Support: iOS <=7 - 8 only
- if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
- rbuggyQSA.push( "~=" );
- }
-
- // Support: iOS 8 only
- // https://bugs.webkit.org/show_bug.cgi?id=136851
- // In-page `selector#id sibling-combinator selector` fails
- if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) {
- rbuggyQSA.push( ".#.+[+~]" );
- }
-
- // Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+
- // In some of the document kinds, these selectors wouldn't work natively.
- // This is probably OK but for backwards compatibility we want to maintain
- // handling them through jQuery traversal in jQuery 3.x.
- if ( !el.querySelectorAll( ":checked" ).length ) {
- rbuggyQSA.push( ":checked" );
- }
-
- // Support: Windows 8 Native Apps
- // The type and name attributes are restricted during .innerHTML assignment
- input = document.createElement( "input" );
- input.setAttribute( "type", "hidden" );
- el.appendChild( input ).setAttribute( "name", "D" );
-
- // Support: IE 9 - 11+
- // IE's :disabled selector does not pick up the children of disabled fieldsets
- // Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+
- // In some of the document kinds, these selectors wouldn't work natively.
- // This is probably OK but for backwards compatibility we want to maintain
- // handling them through jQuery traversal in jQuery 3.x.
- documentElement.appendChild( el ).disabled = true;
- if ( el.querySelectorAll( ":disabled" ).length !== 2 ) {
- rbuggyQSA.push( ":enabled", ":disabled" );
- }
-
- // Support: IE 11+, Edge 15 - 18+
- // IE 11/Edge don't find elements on a `[name='']` query in some cases.
- // Adding a temporary attribute to the document before the selection works
- // around the issue.
- // Interestingly, IE 10 & older don't seem to have the issue.
- input = document.createElement( "input" );
- input.setAttribute( "name", "" );
- el.appendChild( input );
- if ( !el.querySelectorAll( "[name='']" ).length ) {
- rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" +
- whitespace + "*(?:''|\"\")" );
- }
- } );
-
- if ( !support.cssHas ) {
-
- // Support: Chrome 105 - 110+, Safari 15.4 - 16.3+
- // Our regular `try-catch` mechanism fails to detect natively-unsupported
- // pseudo-classes inside `:has()` (such as `:has(:contains("Foo"))`)
- // in browsers that parse the `:has()` argument as a forgiving selector list.
- // https://drafts.csswg.org/selectors/#relational now requires the argument
- // to be parsed unforgivingly, but browsers have not yet fully adjusted.
- rbuggyQSA.push( ":has" );
- }
-
- rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) );
-
- /* Sorting
- ---------------------------------------------------------------------- */
-
- // Document order sorting
- sortOrder = function( a, b ) {
-
- // Flag for duplicate removal
- if ( a === b ) {
- hasDuplicate = true;
- return 0;
- }
-
- // Sort on method existence if only one input has compareDocumentPosition
- var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
- if ( compare ) {
- return compare;
- }
-
- // Calculate position if both inputs belong to the same document
- // Support: IE 11+, Edge 17 - 18+
- // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
- // two documents; shallow comparisons work.
- // eslint-disable-next-line eqeqeq
- compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ?
- a.compareDocumentPosition( b ) :
-
- // Otherwise we know they are disconnected
- 1;
-
- // Disconnected nodes
- if ( compare & 1 ||
- ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) {
-
- // Choose the first element that is related to our preferred document
- // Support: IE 11+, Edge 17 - 18+
- // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
- // two documents; shallow comparisons work.
- // eslint-disable-next-line eqeqeq
- if ( a === document || a.ownerDocument == preferredDoc &&
- find.contains( preferredDoc, a ) ) {
- return -1;
- }
-
- // Support: IE 11+, Edge 17 - 18+
- // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
- // two documents; shallow comparisons work.
- // eslint-disable-next-line eqeqeq
- if ( b === document || b.ownerDocument == preferredDoc &&
- find.contains( preferredDoc, b ) ) {
- return 1;
- }
-
- // Maintain original order
- return sortInput ?
- ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
- 0;
- }
-
- return compare & 4 ? -1 : 1;
- };
-
- return document;
-}
-
-find.matches = function( expr, elements ) {
- return find( expr, null, null, elements );
-};
-
-find.matchesSelector = function( elem, expr ) {
- setDocument( elem );
-
- if ( documentIsHTML &&
- !nonnativeSelectorCache[ expr + " " ] &&
- ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) {
-
- try {
- var ret = matches.call( elem, expr );
-
- // IE 9's matchesSelector returns false on disconnected nodes
- if ( ret || support.disconnectedMatch ||
-
- // As well, disconnected nodes are said to be in a document
- // fragment in IE 9
- elem.document && elem.document.nodeType !== 11 ) {
- return ret;
- }
- } catch ( e ) {
- nonnativeSelectorCache( expr, true );
- }
- }
-
- return find( expr, document, null, [ elem ] ).length > 0;
-};
-
-find.contains = function( context, elem ) {
-
- // Set document vars if needed
- // Support: IE 11+, Edge 17 - 18+
- // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
- // two documents; shallow comparisons work.
- // eslint-disable-next-line eqeqeq
- if ( ( context.ownerDocument || context ) != document ) {
- setDocument( context );
- }
- return jQuery.contains( context, elem );
-};
-
-
-find.attr = function( elem, name ) {
-
- // Set document vars if needed
- // Support: IE 11+, Edge 17 - 18+
- // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
- // two documents; shallow comparisons work.
- // eslint-disable-next-line eqeqeq
- if ( ( elem.ownerDocument || elem ) != document ) {
- setDocument( elem );
- }
-
- var fn = Expr.attrHandle[ name.toLowerCase() ],
-
- // Don't get fooled by Object.prototype properties (see trac-13807)
- val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
- fn( elem, name, !documentIsHTML ) :
- undefined;
-
- if ( val !== undefined ) {
- return val;
- }
-
- return elem.getAttribute( name );
-};
-
-find.error = function( msg ) {
- throw new Error( "Syntax error, unrecognized expression: " + msg );
-};
-
-/**
- * Document sorting and removing duplicates
- * @param {ArrayLike} results
- */
-jQuery.uniqueSort = function( results ) {
- var elem,
- duplicates = [],
- j = 0,
- i = 0;
-
- // Unless we *know* we can detect duplicates, assume their presence
- //
- // Support: Android <=4.0+
- // Testing for detecting duplicates is unpredictable so instead assume we can't
- // depend on duplicate detection in all browsers without a stable sort.
- hasDuplicate = !support.sortStable;
- sortInput = !support.sortStable && slice.call( results, 0 );
- sort.call( results, sortOrder );
-
- if ( hasDuplicate ) {
- while ( ( elem = results[ i++ ] ) ) {
- if ( elem === results[ i ] ) {
- j = duplicates.push( i );
- }
- }
- while ( j-- ) {
- splice.call( results, duplicates[ j ], 1 );
- }
- }
-
- // Clear input after sorting to release objects
- // See https://github.com/jquery/sizzle/pull/225
- sortInput = null;
-
- return results;
-};
-
-jQuery.fn.uniqueSort = function() {
- return this.pushStack( jQuery.uniqueSort( slice.apply( this ) ) );
-};
-
-Expr = jQuery.expr = {
-
- // Can be adjusted by the user
- cacheLength: 50,
-
- createPseudo: markFunction,
-
- match: matchExpr,
-
- attrHandle: {},
-
- find: {},
-
- relative: {
- ">": { dir: "parentNode", first: true },
- " ": { dir: "parentNode" },
- "+": { dir: "previousSibling", first: true },
- "~": { dir: "previousSibling" }
- },
-
- preFilter: {
- ATTR: function( match ) {
- match[ 1 ] = match[ 1 ].replace( runescape, funescape );
-
- // Move the given value to match[3] whether quoted or unquoted
- match[ 3 ] = ( match[ 3 ] || match[ 4 ] || match[ 5 ] || "" )
- .replace( runescape, funescape );
-
- if ( match[ 2 ] === "~=" ) {
- match[ 3 ] = " " + match[ 3 ] + " ";
- }
-
- return match.slice( 0, 4 );
- },
-
- CHILD: function( match ) {
-
- /* matches from matchExpr["CHILD"]
- 1 type (only|nth|...)
- 2 what (child|of-type)
- 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
- 4 xn-component of xn+y argument ([+-]?\d*n|)
- 5 sign of xn-component
- 6 x of xn-component
- 7 sign of y-component
- 8 y of y-component
- */
- match[ 1 ] = match[ 1 ].toLowerCase();
-
- if ( match[ 1 ].slice( 0, 3 ) === "nth" ) {
-
- // nth-* requires argument
- if ( !match[ 3 ] ) {
- find.error( match[ 0 ] );
- }
-
- // numeric x and y parameters for Expr.filter.CHILD
- // remember that false/true cast respectively to 0/1
- match[ 4 ] = +( match[ 4 ] ?
- match[ 5 ] + ( match[ 6 ] || 1 ) :
- 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" )
- );
- match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" );
-
- // other types prohibit arguments
- } else if ( match[ 3 ] ) {
- find.error( match[ 0 ] );
- }
-
- return match;
- },
-
- PSEUDO: function( match ) {
- var excess,
- unquoted = !match[ 6 ] && match[ 2 ];
-
- if ( matchExpr.CHILD.test( match[ 0 ] ) ) {
- return null;
- }
-
- // Accept quoted arguments as-is
- if ( match[ 3 ] ) {
- match[ 2 ] = match[ 4 ] || match[ 5 ] || "";
-
- // Strip excess characters from unquoted arguments
- } else if ( unquoted && rpseudo.test( unquoted ) &&
-
- // Get excess from tokenize (recursively)
- ( excess = tokenize( unquoted, true ) ) &&
-
- // advance to the next closing parenthesis
- ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) {
-
- // excess is a negative index
- match[ 0 ] = match[ 0 ].slice( 0, excess );
- match[ 2 ] = unquoted.slice( 0, excess );
- }
-
- // Return only captures needed by the pseudo filter method (type and argument)
- return match.slice( 0, 3 );
- }
- },
-
- filter: {
-
- TAG: function( nodeNameSelector ) {
- var expectedNodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
- return nodeNameSelector === "*" ?
- function() {
- return true;
- } :
- function( elem ) {
- return nodeName( elem, expectedNodeName );
- };
- },
-
- CLASS: function( className ) {
- var pattern = classCache[ className + " " ];
-
- return pattern ||
- ( pattern = new RegExp( "(^|" + whitespace + ")" + className +
- "(" + whitespace + "|$)" ) ) &&
- classCache( className, function( elem ) {
- return pattern.test(
- typeof elem.className === "string" && elem.className ||
- typeof elem.getAttribute !== "undefined" &&
- elem.getAttribute( "class" ) ||
- ""
- );
- } );
- },
-
- ATTR: function( name, operator, check ) {
- return function( elem ) {
- var result = find.attr( elem, name );
-
- if ( result == null ) {
- return operator === "!=";
- }
- if ( !operator ) {
- return true;
- }
-
- result += "";
-
- if ( operator === "=" ) {
- return result === check;
- }
- if ( operator === "!=" ) {
- return result !== check;
- }
- if ( operator === "^=" ) {
- return check && result.indexOf( check ) === 0;
- }
- if ( operator === "*=" ) {
- return check && result.indexOf( check ) > -1;
- }
- if ( operator === "$=" ) {
- return check && result.slice( -check.length ) === check;
- }
- if ( operator === "~=" ) {
- return ( " " + result.replace( rwhitespace, " " ) + " " )
- .indexOf( check ) > -1;
- }
- if ( operator === "|=" ) {
- return result === check || result.slice( 0, check.length + 1 ) === check + "-";
- }
-
- return false;
- };
- },
-
- CHILD: function( type, what, _argument, first, last ) {
- var simple = type.slice( 0, 3 ) !== "nth",
- forward = type.slice( -4 ) !== "last",
- ofType = what === "of-type";
-
- return first === 1 && last === 0 ?
-
- // Shortcut for :nth-*(n)
- function( elem ) {
- return !!elem.parentNode;
- } :
-
- function( elem, _context, xml ) {
- var cache, outerCache, node, nodeIndex, start,
- dir = simple !== forward ? "nextSibling" : "previousSibling",
- parent = elem.parentNode,
- name = ofType && elem.nodeName.toLowerCase(),
- useCache = !xml && !ofType,
- diff = false;
-
- if ( parent ) {
-
- // :(first|last|only)-(child|of-type)
- if ( simple ) {
- while ( dir ) {
- node = elem;
- while ( ( node = node[ dir ] ) ) {
- if ( ofType ?
- nodeName( node, name ) :
- node.nodeType === 1 ) {
-
- return false;
- }
- }
-
- // Reverse direction for :only-* (if we haven't yet done so)
- start = dir = type === "only" && !start && "nextSibling";
- }
- return true;
- }
-
- start = [ forward ? parent.firstChild : parent.lastChild ];
-
- // non-xml :nth-child(...) stores cache data on `parent`
- if ( forward && useCache ) {
-
- // Seek `elem` from a previously-cached index
- outerCache = parent[ expando ] || ( parent[ expando ] = {} );
- cache = outerCache[ type ] || [];
- nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
- diff = nodeIndex && cache[ 2 ];
- node = nodeIndex && parent.childNodes[ nodeIndex ];
-
- while ( ( node = ++nodeIndex && node && node[ dir ] ||
-
- // Fallback to seeking `elem` from the start
- ( diff = nodeIndex = 0 ) || start.pop() ) ) {
-
- // When found, cache indexes on `parent` and break
- if ( node.nodeType === 1 && ++diff && node === elem ) {
- outerCache[ type ] = [ dirruns, nodeIndex, diff ];
- break;
- }
- }
-
- } else {
-
- // Use previously-cached element index if available
- if ( useCache ) {
- outerCache = elem[ expando ] || ( elem[ expando ] = {} );
- cache = outerCache[ type ] || [];
- nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
- diff = nodeIndex;
- }
-
- // xml :nth-child(...)
- // or :nth-last-child(...) or :nth(-last)?-of-type(...)
- if ( diff === false ) {
-
- // Use the same loop as above to seek `elem` from the start
- while ( ( node = ++nodeIndex && node && node[ dir ] ||
- ( diff = nodeIndex = 0 ) || start.pop() ) ) {
-
- if ( ( ofType ?
- nodeName( node, name ) :
- node.nodeType === 1 ) &&
- ++diff ) {
-
- // Cache the index of each encountered element
- if ( useCache ) {
- outerCache = node[ expando ] ||
- ( node[ expando ] = {} );
- outerCache[ type ] = [ dirruns, diff ];
- }
-
- if ( node === elem ) {
- break;
- }
- }
- }
- }
- }
-
- // Incorporate the offset, then check against cycle size
- diff -= last;
- return diff === first || ( diff % first === 0 && diff / first >= 0 );
- }
- };
- },
-
- PSEUDO: function( pseudo, argument ) {
-
- // pseudo-class names are case-insensitive
- // https://www.w3.org/TR/selectors/#pseudo-classes
- // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
- // Remember that setFilters inherits from pseudos
- var args,
- fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
- find.error( "unsupported pseudo: " + pseudo );
-
- // The user may use createPseudo to indicate that
- // arguments are needed to create the filter function
- // just as jQuery does
- if ( fn[ expando ] ) {
- return fn( argument );
- }
-
- // But maintain support for old signatures
- if ( fn.length > 1 ) {
- args = [ pseudo, pseudo, "", argument ];
- return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
- markFunction( function( seed, matches ) {
- var idx,
- matched = fn( seed, argument ),
- i = matched.length;
- while ( i-- ) {
- idx = indexOf.call( seed, matched[ i ] );
- seed[ idx ] = !( matches[ idx ] = matched[ i ] );
- }
- } ) :
- function( elem ) {
- return fn( elem, 0, args );
- };
- }
-
- return fn;
- }
- },
-
- pseudos: {
-
- // Potentially complex pseudos
- not: markFunction( function( selector ) {
-
- // Trim the selector passed to compile
- // to avoid treating leading and trailing
- // spaces as combinators
- var input = [],
- results = [],
- matcher = compile( selector.replace( rtrimCSS, "$1" ) );
-
- return matcher[ expando ] ?
- markFunction( function( seed, matches, _context, xml ) {
- var elem,
- unmatched = matcher( seed, null, xml, [] ),
- i = seed.length;
-
- // Match elements unmatched by `matcher`
- while ( i-- ) {
- if ( ( elem = unmatched[ i ] ) ) {
- seed[ i ] = !( matches[ i ] = elem );
- }
- }
- } ) :
- function( elem, _context, xml ) {
- input[ 0 ] = elem;
- matcher( input, null, xml, results );
-
- // Don't keep the element
- // (see https://github.com/jquery/sizzle/issues/299)
- input[ 0 ] = null;
- return !results.pop();
- };
- } ),
-
- has: markFunction( function( selector ) {
- return function( elem ) {
- return find( selector, elem ).length > 0;
- };
- } ),
-
- contains: markFunction( function( text ) {
- text = text.replace( runescape, funescape );
- return function( elem ) {
- return ( elem.textContent || jQuery.text( elem ) ).indexOf( text ) > -1;
- };
- } ),
-
- // "Whether an element is represented by a :lang() selector
- // is based solely on the element's language value
- // being equal to the identifier C,
- // or beginning with the identifier C immediately followed by "-".
- // The matching of C against the element's language value is performed case-insensitively.
- // The identifier C does not have to be a valid language name."
- // https://www.w3.org/TR/selectors/#lang-pseudo
- lang: markFunction( function( lang ) {
-
- // lang value must be a valid identifier
- if ( !ridentifier.test( lang || "" ) ) {
- find.error( "unsupported lang: " + lang );
- }
- lang = lang.replace( runescape, funescape ).toLowerCase();
- return function( elem ) {
- var elemLang;
- do {
- if ( ( elemLang = documentIsHTML ?
- elem.lang :
- elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) {
-
- elemLang = elemLang.toLowerCase();
- return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
- }
- } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 );
- return false;
- };
- } ),
-
- // Miscellaneous
- target: function( elem ) {
- var hash = window.location && window.location.hash;
- return hash && hash.slice( 1 ) === elem.id;
- },
-
- root: function( elem ) {
- return elem === documentElement;
- },
-
- focus: function( elem ) {
- return elem === safeActiveElement() &&
- document.hasFocus() &&
- !!( elem.type || elem.href || ~elem.tabIndex );
- },
-
- // Boolean properties
- enabled: createDisabledPseudo( false ),
- disabled: createDisabledPseudo( true ),
-
- checked: function( elem ) {
-
- // In CSS3, :checked should return both checked and selected elements
- // https://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
- return ( nodeName( elem, "input" ) && !!elem.checked ) ||
- ( nodeName( elem, "option" ) && !!elem.selected );
- },
-
- selected: function( elem ) {
-
- // Support: IE <=11+
- // Accessing the selectedIndex property
- // forces the browser to treat the default option as
- // selected when in an optgroup.
- if ( elem.parentNode ) {
- // eslint-disable-next-line no-unused-expressions
- elem.parentNode.selectedIndex;
- }
-
- return elem.selected === true;
- },
-
- // Contents
- empty: function( elem ) {
-
- // https://www.w3.org/TR/selectors/#empty-pseudo
- // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
- // but not by others (comment: 8; processing instruction: 7; etc.)
- // nodeType < 6 works because attributes (2) do not appear as children
- for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
- if ( elem.nodeType < 6 ) {
- return false;
- }
- }
- return true;
- },
-
- parent: function( elem ) {
- return !Expr.pseudos.empty( elem );
- },
-
- // Element/input types
- header: function( elem ) {
- return rheader.test( elem.nodeName );
- },
-
- input: function( elem ) {
- return rinputs.test( elem.nodeName );
- },
-
- button: function( elem ) {
- return nodeName( elem, "input" ) && elem.type === "button" ||
- nodeName( elem, "button" );
- },
-
- text: function( elem ) {
- var attr;
- return nodeName( elem, "input" ) && elem.type === "text" &&
-
- // Support: IE <10 only
- // New HTML5 attribute values (e.g., "search") appear
- // with elem.type === "text"
- ( ( attr = elem.getAttribute( "type" ) ) == null ||
- attr.toLowerCase() === "text" );
- },
-
- // Position-in-collection
- first: createPositionalPseudo( function() {
- return [ 0 ];
- } ),
-
- last: createPositionalPseudo( function( _matchIndexes, length ) {
- return [ length - 1 ];
- } ),
-
- eq: createPositionalPseudo( function( _matchIndexes, length, argument ) {
- return [ argument < 0 ? argument + length : argument ];
- } ),
-
- even: createPositionalPseudo( function( matchIndexes, length ) {
- var i = 0;
- for ( ; i < length; i += 2 ) {
- matchIndexes.push( i );
- }
- return matchIndexes;
- } ),
-
- odd: createPositionalPseudo( function( matchIndexes, length ) {
- var i = 1;
- for ( ; i < length; i += 2 ) {
- matchIndexes.push( i );
- }
- return matchIndexes;
- } ),
-
- lt: createPositionalPseudo( function( matchIndexes, length, argument ) {
- var i;
-
- if ( argument < 0 ) {
- i = argument + length;
- } else if ( argument > length ) {
- i = length;
- } else {
- i = argument;
- }
-
- for ( ; --i >= 0; ) {
- matchIndexes.push( i );
- }
- return matchIndexes;
- } ),
-
- gt: createPositionalPseudo( function( matchIndexes, length, argument ) {
- var i = argument < 0 ? argument + length : argument;
- for ( ; ++i < length; ) {
- matchIndexes.push( i );
- }
- return matchIndexes;
- } )
- }
-};
-
-Expr.pseudos.nth = Expr.pseudos.eq;
-
-// Add button/input type pseudos
-for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
- Expr.pseudos[ i ] = createInputPseudo( i );
-}
-for ( i in { submit: true, reset: true } ) {
- Expr.pseudos[ i ] = createButtonPseudo( i );
-}
-
-// Easy API for creating new setFilters
-function setFilters() {}
-setFilters.prototype = Expr.filters = Expr.pseudos;
-Expr.setFilters = new setFilters();
-
-function tokenize( selector, parseOnly ) {
- var matched, match, tokens, type,
- soFar, groups, preFilters,
- cached = tokenCache[ selector + " " ];
-
- if ( cached ) {
- return parseOnly ? 0 : cached.slice( 0 );
- }
-
- soFar = selector;
- groups = [];
- preFilters = Expr.preFilter;
-
- while ( soFar ) {
-
- // Comma and first run
- if ( !matched || ( match = rcomma.exec( soFar ) ) ) {
- if ( match ) {
-
- // Don't consume trailing commas as valid
- soFar = soFar.slice( match[ 0 ].length ) || soFar;
- }
- groups.push( ( tokens = [] ) );
- }
-
- matched = false;
-
- // Combinators
- if ( ( match = rleadingCombinator.exec( soFar ) ) ) {
- matched = match.shift();
- tokens.push( {
- value: matched,
-
- // Cast descendant combinators to space
- type: match[ 0 ].replace( rtrimCSS, " " )
- } );
- soFar = soFar.slice( matched.length );
- }
-
- // Filters
- for ( type in Expr.filter ) {
- if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] ||
- ( match = preFilters[ type ]( match ) ) ) ) {
- matched = match.shift();
- tokens.push( {
- value: matched,
- type: type,
- matches: match
- } );
- soFar = soFar.slice( matched.length );
- }
- }
-
- if ( !matched ) {
- break;
- }
- }
-
- // Return the length of the invalid excess
- // if we're just parsing
- // Otherwise, throw an error or return tokens
- if ( parseOnly ) {
- return soFar.length;
- }
-
- return soFar ?
- find.error( selector ) :
-
- // Cache the tokens
- tokenCache( selector, groups ).slice( 0 );
-}
-
-function toSelector( tokens ) {
- var i = 0,
- len = tokens.length,
- selector = "";
- for ( ; i < len; i++ ) {
- selector += tokens[ i ].value;
- }
- return selector;
-}
-
-function addCombinator( matcher, combinator, base ) {
- var dir = combinator.dir,
- skip = combinator.next,
- key = skip || dir,
- checkNonElements = base && key === "parentNode",
- doneName = done++;
-
- return combinator.first ?
-
- // Check against closest ancestor/preceding element
- function( elem, context, xml ) {
- while ( ( elem = elem[ dir ] ) ) {
- if ( elem.nodeType === 1 || checkNonElements ) {
- return matcher( elem, context, xml );
- }
- }
- return false;
- } :
-
- // Check against all ancestor/preceding elements
- function( elem, context, xml ) {
- var oldCache, outerCache,
- newCache = [ dirruns, doneName ];
-
- // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching
- if ( xml ) {
- while ( ( elem = elem[ dir ] ) ) {
- if ( elem.nodeType === 1 || checkNonElements ) {
- if ( matcher( elem, context, xml ) ) {
- return true;
- }
- }
- }
- } else {
- while ( ( elem = elem[ dir ] ) ) {
- if ( elem.nodeType === 1 || checkNonElements ) {
- outerCache = elem[ expando ] || ( elem[ expando ] = {} );
-
- if ( skip && nodeName( elem, skip ) ) {
- elem = elem[ dir ] || elem;
- } else if ( ( oldCache = outerCache[ key ] ) &&
- oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
-
- // Assign to newCache so results back-propagate to previous elements
- return ( newCache[ 2 ] = oldCache[ 2 ] );
- } else {
-
- // Reuse newcache so results back-propagate to previous elements
- outerCache[ key ] = newCache;
-
- // A match means we're done; a fail means we have to keep checking
- if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) {
- return true;
- }
- }
- }
- }
- }
- return false;
- };
-}
-
-function elementMatcher( matchers ) {
- return matchers.length > 1 ?
- function( elem, context, xml ) {
- var i = matchers.length;
- while ( i-- ) {
- if ( !matchers[ i ]( elem, context, xml ) ) {
- return false;
- }
- }
- return true;
- } :
- matchers[ 0 ];
-}
-
-function multipleContexts( selector, contexts, results ) {
- var i = 0,
- len = contexts.length;
- for ( ; i < len; i++ ) {
- find( selector, contexts[ i ], results );
- }
- return results;
-}
-
-function condense( unmatched, map, filter, context, xml ) {
- var elem,
- newUnmatched = [],
- i = 0,
- len = unmatched.length,
- mapped = map != null;
-
- for ( ; i < len; i++ ) {
- if ( ( elem = unmatched[ i ] ) ) {
- if ( !filter || filter( elem, context, xml ) ) {
- newUnmatched.push( elem );
- if ( mapped ) {
- map.push( i );
- }
- }
- }
- }
-
- return newUnmatched;
-}
-
-function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
- if ( postFilter && !postFilter[ expando ] ) {
- postFilter = setMatcher( postFilter );
- }
- if ( postFinder && !postFinder[ expando ] ) {
- postFinder = setMatcher( postFinder, postSelector );
- }
- return markFunction( function( seed, results, context, xml ) {
- var temp, i, elem, matcherOut,
- preMap = [],
- postMap = [],
- preexisting = results.length,
-
- // Get initial elements from seed or context
- elems = seed ||
- multipleContexts( selector || "*",
- context.nodeType ? [ context ] : context, [] ),
-
- // Prefilter to get matcher input, preserving a map for seed-results synchronization
- matcherIn = preFilter && ( seed || !selector ) ?
- condense( elems, preMap, preFilter, context, xml ) :
- elems;
-
- if ( matcher ) {
-
- // If we have a postFinder, or filtered seed, or non-seed postFilter
- // or preexisting results,
- matcherOut = postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
-
- // ...intermediate processing is necessary
- [] :
-
- // ...otherwise use results directly
- results;
-
- // Find primary matches
- matcher( matcherIn, matcherOut, context, xml );
- } else {
- matcherOut = matcherIn;
- }
-
- // Apply postFilter
- if ( postFilter ) {
- temp = condense( matcherOut, postMap );
- postFilter( temp, [], context, xml );
-
- // Un-match failing elements by moving them back to matcherIn
- i = temp.length;
- while ( i-- ) {
- if ( ( elem = temp[ i ] ) ) {
- matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem );
- }
- }
- }
-
- if ( seed ) {
- if ( postFinder || preFilter ) {
- if ( postFinder ) {
-
- // Get the final matcherOut by condensing this intermediate into postFinder contexts
- temp = [];
- i = matcherOut.length;
- while ( i-- ) {
- if ( ( elem = matcherOut[ i ] ) ) {
-
- // Restore matcherIn since elem is not yet a final match
- temp.push( ( matcherIn[ i ] = elem ) );
- }
- }
- postFinder( null, ( matcherOut = [] ), temp, xml );
- }
-
- // Move matched elements from seed to results to keep them synchronized
- i = matcherOut.length;
- while ( i-- ) {
- if ( ( elem = matcherOut[ i ] ) &&
- ( temp = postFinder ? indexOf.call( seed, elem ) : preMap[ i ] ) > -1 ) {
-
- seed[ temp ] = !( results[ temp ] = elem );
- }
- }
- }
-
- // Add elements to results, through postFinder if defined
- } else {
- matcherOut = condense(
- matcherOut === results ?
- matcherOut.splice( preexisting, matcherOut.length ) :
- matcherOut
- );
- if ( postFinder ) {
- postFinder( null, results, matcherOut, xml );
- } else {
- push.apply( results, matcherOut );
- }
- }
- } );
-}
-
-function matcherFromTokens( tokens ) {
- var checkContext, matcher, j,
- len = tokens.length,
- leadingRelative = Expr.relative[ tokens[ 0 ].type ],
- implicitRelative = leadingRelative || Expr.relative[ " " ],
- i = leadingRelative ? 1 : 0,
-
- // The foundational matcher ensures that elements are reachable from top-level context(s)
- matchContext = addCombinator( function( elem ) {
- return elem === checkContext;
- }, implicitRelative, true ),
- matchAnyContext = addCombinator( function( elem ) {
- return indexOf.call( checkContext, elem ) > -1;
- }, implicitRelative, true ),
- matchers = [ function( elem, context, xml ) {
-
- // Support: IE 11+, Edge 17 - 18+
- // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
- // two documents; shallow comparisons work.
- // eslint-disable-next-line eqeqeq
- var ret = ( !leadingRelative && ( xml || context != outermostContext ) ) || (
- ( checkContext = context ).nodeType ?
- matchContext( elem, context, xml ) :
- matchAnyContext( elem, context, xml ) );
-
- // Avoid hanging onto element
- // (see https://github.com/jquery/sizzle/issues/299)
- checkContext = null;
- return ret;
- } ];
-
- for ( ; i < len; i++ ) {
- if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) {
- matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ];
- } else {
- matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches );
-
- // Return special upon seeing a positional matcher
- if ( matcher[ expando ] ) {
-
- // Find the next relative operator (if any) for proper handling
- j = ++i;
- for ( ; j < len; j++ ) {
- if ( Expr.relative[ tokens[ j ].type ] ) {
- break;
- }
- }
- return setMatcher(
- i > 1 && elementMatcher( matchers ),
- i > 1 && toSelector(
-
- // If the preceding token was a descendant combinator, insert an implicit any-element `*`
- tokens.slice( 0, i - 1 )
- .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } )
- ).replace( rtrimCSS, "$1" ),
- matcher,
- i < j && matcherFromTokens( tokens.slice( i, j ) ),
- j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ),
- j < len && toSelector( tokens )
- );
- }
- matchers.push( matcher );
- }
- }
-
- return elementMatcher( matchers );
-}
-
-function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
- var bySet = setMatchers.length > 0,
- byElement = elementMatchers.length > 0,
- superMatcher = function( seed, context, xml, results, outermost ) {
- var elem, j, matcher,
- matchedCount = 0,
- i = "0",
- unmatched = seed && [],
- setMatched = [],
- contextBackup = outermostContext,
-
- // We must always have either seed elements or outermost context
- elems = seed || byElement && Expr.find.TAG( "*", outermost ),
-
- // Use integer dirruns iff this is the outermost matcher
- dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ),
- len = elems.length;
-
- if ( outermost ) {
-
- // Support: IE 11+, Edge 17 - 18+
- // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
- // two documents; shallow comparisons work.
- // eslint-disable-next-line eqeqeq
- outermostContext = context == document || context || outermost;
- }
-
- // Add elements passing elementMatchers directly to results
- // Support: iOS <=7 - 9 only
- // Tolerate NodeList properties (IE: "length"; Safari: <number>) matching
- // elements by id. (see trac-14142)
- for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) {
- if ( byElement && elem ) {
- j = 0;
-
- // Support: IE 11+, Edge 17 - 18+
- // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
- // two documents; shallow comparisons work.
- // eslint-disable-next-line eqeqeq
- if ( !context && elem.ownerDocument != document ) {
- setDocument( elem );
- xml = !documentIsHTML;
- }
- while ( ( matcher = elementMatchers[ j++ ] ) ) {
- if ( matcher( elem, context || document, xml ) ) {
- push.call( results, elem );
- break;
- }
- }
- if ( outermost ) {
- dirruns = dirrunsUnique;
- }
- }
-
- // Track unmatched elements for set filters
- if ( bySet ) {
-
- // They will have gone through all possible matchers
- if ( ( elem = !matcher && elem ) ) {
- matchedCount--;
- }
-
- // Lengthen the array for every element, matched or not
- if ( seed ) {
- unmatched.push( elem );
- }
- }
- }
-
- // `i` is now the count of elements visited above, and adding it to `matchedCount`
- // makes the latter nonnegative.
- matchedCount += i;
-
- // Apply set filters to unmatched elements
- // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
- // equals `i`), unless we didn't visit _any_ elements in the above loop because we have
- // no element matchers and no seed.
- // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
- // case, which will result in a "00" `matchedCount` that differs from `i` but is also
- // numerically zero.
- if ( bySet && i !== matchedCount ) {
- j = 0;
- while ( ( matcher = setMatchers[ j++ ] ) ) {
- matcher( unmatched, setMatched, context, xml );
- }
-
- if ( seed ) {
-
- // Reintegrate element matches to eliminate the need for sorting
- if ( matchedCount > 0 ) {
- while ( i-- ) {
- if ( !( unmatched[ i ] || setMatched[ i ] ) ) {
- setMatched[ i ] = pop.call( results );
- }
- }
- }
-
- // Discard index placeholder values to get only actual matches
- setMatched = condense( setMatched );
- }
-
- // Add matches to results
- push.apply( results, setMatched );
-
- // Seedless set matches succeeding multiple successful matchers stipulate sorting
- if ( outermost && !seed && setMatched.length > 0 &&
- ( matchedCount + setMatchers.length ) > 1 ) {
-
- jQuery.uniqueSort( results );
- }
- }
-
- // Override manipulation of globals by nested matchers
- if ( outermost ) {
- dirruns = dirrunsUnique;
- outermostContext = contextBackup;
- }
-
- return unmatched;
- };
-
- return bySet ?
- markFunction( superMatcher ) :
- superMatcher;
-}
-
-function compile( selector, match /* Internal Use Only */ ) {
- var i,
- setMatchers = [],
- elementMatchers = [],
- cached = compilerCache[ selector + " " ];
-
- if ( !cached ) {
-
- // Generate a function of recursive functions that can be used to check each element
- if ( !match ) {
- match = tokenize( selector );
- }
- i = match.length;
- while ( i-- ) {
- cached = matcherFromTokens( match[ i ] );
- if ( cached[ expando ] ) {
- setMatchers.push( cached );
- } else {
- elementMatchers.push( cached );
- }
- }
-
- // Cache the compiled function
- cached = compilerCache( selector,
- matcherFromGroupMatchers( elementMatchers, setMatchers ) );
-
- // Save selector and tokenization
- cached.selector = selector;
- }
- return cached;
-}
-
-/**
- * A low-level selection function that works with jQuery's compiled
- * selector functions
- * @param {String|Function} selector A selector or a pre-compiled
- * selector function built with jQuery selector compile
- * @param {Element} context
- * @param {Array} [results]
- * @param {Array} [seed] A set of elements to match against
- */
-function select( selector, context, results, seed ) {
- var i, tokens, token, type, find,
- compiled = typeof selector === "function" && selector,
- match = !seed && tokenize( ( selector = compiled.selector || selector ) );
-
- results = results || [];
-
- // Try to minimize operations if there is only one selector in the list and no seed
- // (the latter of which guarantees us context)
- if ( match.length === 1 ) {
-
- // Reduce context if the leading compound selector is an ID
- tokens = match[ 0 ] = match[ 0 ].slice( 0 );
- if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" &&
- context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) {
-
- context = ( Expr.find.ID(
- token.matches[ 0 ].replace( runescape, funescape ),
- context
- ) || [] )[ 0 ];
- if ( !context ) {
- return results;
-
- // Precompiled matchers will still verify ancestry, so step up a level
- } else if ( compiled ) {
- context = context.parentNode;
- }
-
- selector = selector.slice( tokens.shift().value.length );
- }
-
- // Fetch a seed set for right-to-left matching
- i = matchExpr.needsContext.test( selector ) ? 0 : tokens.length;
- while ( i-- ) {
- token = tokens[ i ];
-
- // Abort if we hit a combinator
- if ( Expr.relative[ ( type = token.type ) ] ) {
- break;
- }
- if ( ( find = Expr.find[ type ] ) ) {
-
- // Search, expanding context for leading sibling combinators
- if ( ( seed = find(
- token.matches[ 0 ].replace( runescape, funescape ),
- rsibling.test( tokens[ 0 ].type ) &&
- testContext( context.parentNode ) || context
- ) ) ) {
-
- // If seed is empty or no tokens remain, we can return early
- tokens.splice( i, 1 );
- selector = seed.length && toSelector( tokens );
- if ( !selector ) {
- push.apply( results, seed );
- return results;
- }
-
- break;
- }
- }
- }
- }
-
- // Compile and execute a filtering function if one is not provided
- // Provide `match` to avoid retokenization if we modified the selector above
- ( compiled || compile( selector, match ) )(
- seed,
- context,
- !documentIsHTML,
- results,
- !context || rsibling.test( selector ) && testContext( context.parentNode ) || context
- );
- return results;
-}
-
-// One-time assignments
-
-// Support: Android <=4.0 - 4.1+
-// Sort stability
-support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando;
-
-// Initialize against the default document
-setDocument();
-
-// Support: Android <=4.0 - 4.1+
-// Detached nodes confoundingly follow *each other*
-support.sortDetached = assert( function( el ) {
-
- // Should return 1, but returns 4 (following)
- return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1;
-} );
-
-jQuery.find = find;
-
-// Deprecated
-jQuery.expr[ ":" ] = jQuery.expr.pseudos;
-jQuery.unique = jQuery.uniqueSort;
-
-// These have always been private, but they used to be documented
-// as part of Sizzle so let's maintain them in the 3.x line
-// for backwards compatibility purposes.
-find.compile = compile;
-find.select = select;
-find.setDocument = setDocument;
-
-find.escape = jQuery.escapeSelector;
-find.getText = jQuery.text;
-find.isXML = jQuery.isXMLDoc;
-find.selectors = jQuery.expr;
-find.support = jQuery.support;
-find.uniqueSort = jQuery.uniqueSort;
-
- /* eslint-enable */
-
-} )();
-
-
-var dir = function( elem, dir, until ) {
- var matched = [],
- truncate = until !== undefined;
-
- while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) {
- if ( elem.nodeType === 1 ) {
- if ( truncate && jQuery( elem ).is( until ) ) {
- break;
- }
- matched.push( elem );
- }
- }
- return matched;
-};
-
-
-var siblings = function( n, elem ) {
- var matched = [];
-
- for ( ; n; n = n.nextSibling ) {
- if ( n.nodeType === 1 && n !== elem ) {
- matched.push( n );
- }
- }
-
- return matched;
-};
-
-
-var rneedsContext = jQuery.expr.match.needsContext;
-
-var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );
-
-
-
-// Implement the identical functionality for filter and not
-function winnow( elements, qualifier, not ) {
- if ( isFunction( qualifier ) ) {
- return jQuery.grep( elements, function( elem, i ) {
- return !!qualifier.call( elem, i, elem ) !== not;
- } );
- }
-
- // Single element
- if ( qualifier.nodeType ) {
- return jQuery.grep( elements, function( elem ) {
- return ( elem === qualifier ) !== not;
- } );
- }
-
- // Arraylike of elements (jQuery, arguments, Array)
- if ( typeof qualifier !== "string" ) {
- return jQuery.grep( elements, function( elem ) {
- return ( indexOf.call( qualifier, elem ) > -1 ) !== not;
- } );
- }
-
- // Filtered directly for both simple and complex selectors
- return jQuery.filter( qualifier, elements, not );
-}
-
-jQuery.filter = function( expr, elems, not ) {
- var elem = elems[ 0 ];
-
- if ( not ) {
- expr = ":not(" + expr + ")";
- }
-
- if ( elems.length === 1 && elem.nodeType === 1 ) {
- return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [];
- }
-
- return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
- return elem.nodeType === 1;
- } ) );
-};
-
-jQuery.fn.extend( {
- find: function( selector ) {
- var i, ret,
- len = this.length,
- self = this;
-
- if ( typeof selector !== "string" ) {
- return this.pushStack( jQuery( selector ).filter( function() {
- for ( i = 0; i < len; i++ ) {
- if ( jQuery.contains( self[ i ], this ) ) {
- return true;
- }
- }
- } ) );
- }
-
- ret = this.pushStack( [] );
-
- for ( i = 0; i < len; i++ ) {
- jQuery.find( selector, self[ i ], ret );
- }
-
- return len > 1 ? jQuery.uniqueSort( ret ) : ret;
- },
- filter: function( selector ) {
- return this.pushStack( winnow( this, selector || [], false ) );
- },
- not: function( selector ) {
- return this.pushStack( winnow( this, selector || [], true ) );
- },
- is: function( selector ) {
- return !!winnow(
- this,
-
- // If this is a positional/relative selector, check membership in the returned set
- // so $("p:first").is("p:last") won't return true for a doc with two "p".
- typeof selector === "string" && rneedsContext.test( selector ) ?
- jQuery( selector ) :
- selector || [],
- false
- ).length;
- }
-} );
-
-
-// Initialize a jQuery object
-
-
-// A central reference to the root jQuery(document)
-var rootjQuery,
-
- // A simple way to check for HTML strings
- // Prioritize #id over <tag> to avoid XSS via location.hash (trac-9521)
- // Strict HTML recognition (trac-11290: must start with <)
- // Shortcut simple #id case for speed
- rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
-
- init = jQuery.fn.init = function( selector, context, root ) {
- var match, elem;
-
- // HANDLE: $(""), $(null), $(undefined), $(false)
- if ( !selector ) {
- return this;
- }
-
- // Method init() accepts an alternate rootjQuery
- // so migrate can support jQuery.sub (gh-2101)
- root = root || rootjQuery;
-
- // Handle HTML strings
- if ( typeof selector === "string" ) {
- if ( selector[ 0 ] === "<" &&
- selector[ selector.length - 1 ] === ">" &&
- selector.length >= 3 ) {
-
- // Assume that strings that start and end with <> are HTML and skip the regex check
- match = [ null, selector, null ];
-
- } else {
- match = rquickExpr.exec( selector );
- }
-
- // Match html or make sure no context is specified for #id
- if ( match && ( match[ 1 ] || !context ) ) {
-
- // HANDLE: $(html) -> $(array)
- if ( match[ 1 ] ) {
- context = context instanceof jQuery ? context[ 0 ] : context;
-
- // Option to run scripts is true for back-compat
- // Intentionally let the error be thrown if parseHTML is not present
- jQuery.merge( this, jQuery.parseHTML(
- match[ 1 ],
- context && context.nodeType ? context.ownerDocument || context : document,
- true
- ) );
-
- // HANDLE: $(html, props)
- if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
- for ( match in context ) {
-
- // Properties of context are called as methods if possible
- if ( isFunction( this[ match ] ) ) {
- this[ match ]( context[ match ] );
-
- // ...and otherwise set as attributes
- } else {
- this.attr( match, context[ match ] );
- }
- }
- }
-
- return this;
-
- // HANDLE: $(#id)
- } else {
- elem = document.getElementById( match[ 2 ] );
-
- if ( elem ) {
-
- // Inject the element directly into the jQuery object
- this[ 0 ] = elem;
- this.length = 1;
- }
- return this;
- }
-
- // HANDLE: $(expr, $(...))
- } else if ( !context || context.jquery ) {
- return ( context || root ).find( selector );
-
- // HANDLE: $(expr, context)
- // (which is just equivalent to: $(context).find(expr)
- } else {
- return this.constructor( context ).find( selector );
- }
-
- // HANDLE: $(DOMElement)
- } else if ( selector.nodeType ) {
- this[ 0 ] = selector;
- this.length = 1;
- return this;
-
- // HANDLE: $(function)
- // Shortcut for document ready
- } else if ( isFunction( selector ) ) {
- return root.ready !== undefined ?
- root.ready( selector ) :
-
- // Execute immediately if ready is not present
- selector( jQuery );
- }
-
- return jQuery.makeArray( selector, this );
- };
-
-// Give the init function the jQuery prototype for later instantiation
-init.prototype = jQuery.fn;
-
-// Initialize central reference
-rootjQuery = jQuery( document );
-
-
-var rparentsprev = /^(?:parents|prev(?:Until|All))/,
-
- // Methods guaranteed to produce a unique set when starting from a unique set
- guaranteedUnique = {
- children: true,
- contents: true,
- next: true,
- prev: true
- };
-
-jQuery.fn.extend( {
- has: function( target ) {
- var targets = jQuery( target, this ),
- l = targets.length;
-
- return this.filter( function() {
- var i = 0;
- for ( ; i < l; i++ ) {
- if ( jQuery.contains( this, targets[ i ] ) ) {
- return true;
- }
- }
- } );
- },
-
- closest: function( selectors, context ) {
- var cur,
- i = 0,
- l = this.length,
- matched = [],
- targets = typeof selectors !== "string" && jQuery( selectors );
-
- // Positional selectors never match, since there's no _selection_ context
- if ( !rneedsContext.test( selectors ) ) {
- for ( ; i < l; i++ ) {
- for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {
-
- // Always skip document fragments
- if ( cur.nodeType < 11 && ( targets ?
- targets.index( cur ) > -1 :
-
- // Don't pass non-elements to jQuery#find
- cur.nodeType === 1 &&
- jQuery.find.matchesSelector( cur, selectors ) ) ) {
-
- matched.push( cur );
- break;
- }
- }
- }
- }
-
- return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched );
- },
-
- // Determine the position of an element within the set
- index: function( elem ) {
-
- // No argument, return index in parent
- if ( !elem ) {
- return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
- }
-
- // Index in selector
- if ( typeof elem === "string" ) {
- return indexOf.call( jQuery( elem ), this[ 0 ] );
- }
-
- // Locate the position of the desired element
- return indexOf.call( this,
-
- // If it receives a jQuery object, the first element is used
- elem.jquery ? elem[ 0 ] : elem
- );
- },
-
- add: function( selector, context ) {
- return this.pushStack(
- jQuery.uniqueSort(
- jQuery.merge( this.get(), jQuery( selector, context ) )
- )
- );
- },
-
- addBack: function( selector ) {
- return this.add( selector == null ?
- this.prevObject : this.prevObject.filter( selector )
- );
- }
-} );
-
-function sibling( cur, dir ) {
- while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {}
- return cur;
-}
-
-jQuery.each( {
- parent: function( elem ) {
- var parent = elem.parentNode;
- return parent && parent.nodeType !== 11 ? parent : null;
- },
- parents: function( elem ) {
- return dir( elem, "parentNode" );
- },
- parentsUntil: function( elem, _i, until ) {
- return dir( elem, "parentNode", until );
- },
- next: function( elem ) {
- return sibling( elem, "nextSibling" );
- },
- prev: function( elem ) {
- return sibling( elem, "previousSibling" );
- },
- nextAll: function( elem ) {
- return dir( elem, "nextSibling" );
- },
- prevAll: function( elem ) {
- return dir( elem, "previousSibling" );
- },
- nextUntil: function( elem, _i, until ) {
- return dir( elem, "nextSibling", until );
- },
- prevUntil: function( elem, _i, until ) {
- return dir( elem, "previousSibling", until );
- },
- siblings: function( elem ) {
- return siblings( ( elem.parentNode || {} ).firstChild, elem );
- },
- children: function( elem ) {
- return siblings( elem.firstChild );
- },
- contents: function( elem ) {
- if ( elem.contentDocument != null &&
-
- // Support: IE 11+
- // <object> elements with no `data` attribute has an object
- // `contentDocument` with a `null` prototype.
- getProto( elem.contentDocument ) ) {
-
- return elem.contentDocument;
- }
-
- // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only
- // Treat the template element as a regular one in browsers that
- // don't support it.
- if ( nodeName( elem, "template" ) ) {
- elem = elem.content || elem;
- }
-
- return jQuery.merge( [], elem.childNodes );
- }
-}, function( name, fn ) {
- jQuery.fn[ name ] = function( until, selector ) {
- var matched = jQuery.map( this, fn, until );
-
- if ( name.slice( -5 ) !== "Until" ) {
- selector = until;
- }
-
- if ( selector && typeof selector === "string" ) {
- matched = jQuery.filter( selector, matched );
- }
-
- if ( this.length > 1 ) {
-
- // Remove duplicates
- if ( !guaranteedUnique[ name ] ) {
- jQuery.uniqueSort( matched );
- }
-
- // Reverse order for parents* and prev-derivatives
- if ( rparentsprev.test( name ) ) {
- matched.reverse();
- }
- }
-
- return this.pushStack( matched );
- };
-} );
-var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g );
-
-
-
-// Convert String-formatted options into Object-formatted ones
-function createOptions( options ) {
- var object = {};
- jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) {
- object[ flag ] = true;
- } );
- return object;
-}
-
-/*
- * Create a callback list using the following parameters:
- *
- * options: an optional list of space-separated options that will change how
- * the callback list behaves or a more traditional option object
- *
- * By default a callback list will act like an event callback list and can be
- * "fired" multiple times.
- *
- * Possible options:
- *
- * once: will ensure the callback list can only be fired once (like a Deferred)
- *
- * memory: will keep track of previous values and will call any callback added
- * after the list has been fired right away with the latest "memorized"
- * values (like a Deferred)
- *
- * unique: will ensure a callback can only be added once (no duplicate in the list)
- *
- * stopOnFalse: interrupt callings when a callback returns false
- *
- */
-jQuery.Callbacks = function( options ) {
-
- // Convert options from String-formatted to Object-formatted if needed
- // (we check in cache first)
- options = typeof options === "string" ?
- createOptions( options ) :
- jQuery.extend( {}, options );
-
- var // Flag to know if list is currently firing
- firing,
-
- // Last fire value for non-forgettable lists
- memory,
-
- // Flag to know if list was already fired
- fired,
-
- // Flag to prevent firing
- locked,
-
- // Actual callback list
- list = [],
-
- // Queue of execution data for repeatable lists
- queue = [],
-
- // Index of currently firing callback (modified by add/remove as needed)
- firingIndex = -1,
-
- // Fire callbacks
- fire = function() {
-
- // Enforce single-firing
- locked = locked || options.once;
-
- // Execute callbacks for all pending executions,
- // respecting firingIndex overrides and runtime changes
- fired = firing = true;
- for ( ; queue.length; firingIndex = -1 ) {
- memory = queue.shift();
- while ( ++firingIndex < list.length ) {
-
- // Run callback and check for early termination
- if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
- options.stopOnFalse ) {
-
- // Jump to end and forget the data so .add doesn't re-fire
- firingIndex = list.length;
- memory = false;
- }
- }
- }
-
- // Forget the data if we're done with it
- if ( !options.memory ) {
- memory = false;
- }
-
- firing = false;
-
- // Clean up if we're done firing for good
- if ( locked ) {
-
- // Keep an empty list if we have data for future add calls
- if ( memory ) {
- list = [];
-
- // Otherwise, this object is spent
- } else {
- list = "";
- }
- }
- },
-
- // Actual Callbacks object
- self = {
-
- // Add a callback or a collection of callbacks to the list
- add: function() {
- if ( list ) {
-
- // If we have memory from a past run, we should fire after adding
- if ( memory && !firing ) {
- firingIndex = list.length - 1;
- queue.push( memory );
- }
-
- ( function add( args ) {
- jQuery.each( args, function( _, arg ) {
- if ( isFunction( arg ) ) {
- if ( !options.unique || !self.has( arg ) ) {
- list.push( arg );
- }
- } else if ( arg && arg.length && toType( arg ) !== "string" ) {
-
- // Inspect recursively
- add( arg );
- }
- } );
- } )( arguments );
-
- if ( memory && !firing ) {
- fire();
- }
- }
- return this;
- },
-
- // Remove a callback from the list
- remove: function() {
- jQuery.each( arguments, function( _, arg ) {
- var index;
- while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
- list.splice( index, 1 );
-
- // Handle firing indexes
- if ( index <= firingIndex ) {
- firingIndex--;
- }
- }
- } );
- return this;
- },
-
- // Check if a given callback is in the list.
- // If no argument is given, return whether or not list has callbacks attached.
- has: function( fn ) {
- return fn ?
- jQuery.inArray( fn, list ) > -1 :
- list.length > 0;
- },
-
- // Remove all callbacks from the list
- empty: function() {
- if ( list ) {
- list = [];
- }
- return this;
- },
-
- // Disable .fire and .add
- // Abort any current/pending executions
- // Clear all callbacks and values
- disable: function() {
- locked = queue = [];
- list = memory = "";
- return this;
- },
- disabled: function() {
- return !list;
- },
-
- // Disable .fire
- // Also disable .add unless we have memory (since it would have no effect)
- // Abort any pending executions
- lock: function() {
- locked = queue = [];
- if ( !memory && !firing ) {
- list = memory = "";
- }
- return this;
- },
- locked: function() {
- return !!locked;
- },
-
- // Call all callbacks with the given context and arguments
- fireWith: function( context, args ) {
- if ( !locked ) {
- args = args || [];
- args = [ context, args.slice ? args.slice() : args ];
- queue.push( args );
- if ( !firing ) {
- fire();
- }
- }
- return this;
- },
-
- // Call all the callbacks with the given arguments
- fire: function() {
- self.fireWith( this, arguments );
- return this;
- },
-
- // To know if the callbacks have already been called at least once
- fired: function() {
- return !!fired;
- }
- };
-
- return self;
-};
-
-
-function Identity( v ) {
- return v;
-}
-function Thrower( ex ) {
- throw ex;
-}
-
-function adoptValue( value, resolve, reject, noValue ) {
- var method;
-
- try {
-
- // Check for promise aspect first to privilege synchronous behavior
- if ( value && isFunction( ( method = value.promise ) ) ) {
- method.call( value ).done( resolve ).fail( reject );
-
- // Other thenables
- } else if ( value && isFunction( ( method = value.then ) ) ) {
- method.call( value, resolve, reject );
-
- // Other non-thenables
- } else {
-
- // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer:
- // * false: [ value ].slice( 0 ) => resolve( value )
- // * true: [ value ].slice( 1 ) => resolve()
- resolve.apply( undefined, [ value ].slice( noValue ) );
- }
-
- // For Promises/A+, convert exceptions into rejections
- // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in
- // Deferred#then to conditionally suppress rejection.
- } catch ( value ) {
-
- // Support: Android 4.0 only
- // Strict mode functions invoked without .call/.apply get global-object context
- reject.apply( undefined, [ value ] );
- }
-}
-
-jQuery.extend( {
-
- Deferred: function( func ) {
- var tuples = [
-
- // action, add listener, callbacks,
- // ... .then handlers, argument index, [final state]
- [ "notify", "progress", jQuery.Callbacks( "memory" ),
- jQuery.Callbacks( "memory" ), 2 ],
- [ "resolve", "done", jQuery.Callbacks( "once memory" ),
- jQuery.Callbacks( "once memory" ), 0, "resolved" ],
- [ "reject", "fail", jQuery.Callbacks( "once memory" ),
- jQuery.Callbacks( "once memory" ), 1, "rejected" ]
- ],
- state = "pending",
- promise = {
- state: function() {
- return state;
- },
- always: function() {
- deferred.done( arguments ).fail( arguments );
- return this;
- },
- "catch": function( fn ) {
- return promise.then( null, fn );
- },
-
- // Keep pipe for back-compat
- pipe: function( /* fnDone, fnFail, fnProgress */ ) {
- var fns = arguments;
-
- return jQuery.Deferred( function( newDefer ) {
- jQuery.each( tuples, function( _i, tuple ) {
-
- // Map tuples (progress, done, fail) to arguments (done, fail, progress)
- var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ];
-
- // deferred.progress(function() { bind to newDefer or newDefer.notify })
- // deferred.done(function() { bind to newDefer or newDefer.resolve })
- // deferred.fail(function() { bind to newDefer or newDefer.reject })
- deferred[ tuple[ 1 ] ]( function() {
- var returned = fn && fn.apply( this, arguments );
- if ( returned && isFunction( returned.promise ) ) {
- returned.promise()
- .progress( newDefer.notify )
- .done( newDefer.resolve )
- .fail( newDefer.reject );
- } else {
- newDefer[ tuple[ 0 ] + "With" ](
- this,
- fn ? [ returned ] : arguments
- );
- }
- } );
- } );
- fns = null;
- } ).promise();
- },
- then: function( onFulfilled, onRejected, onProgress ) {
- var maxDepth = 0;
- function resolve( depth, deferred, handler, special ) {
- return function() {
- var that = this,
- args = arguments,
- mightThrow = function() {
- var returned, then;
-
- // Support: Promises/A+ section 2.3.3.3.3
- // https://promisesaplus.com/#point-59
- // Ignore double-resolution attempts
- if ( depth < maxDepth ) {
- return;
- }
-
- returned = handler.apply( that, args );
-
- // Support: Promises/A+ section 2.3.1
- // https://promisesaplus.com/#point-48
- if ( returned === deferred.promise() ) {
- throw new TypeError( "Thenable self-resolution" );
- }
-
- // Support: Promises/A+ sections 2.3.3.1, 3.5
- // https://promisesaplus.com/#point-54
- // https://promisesaplus.com/#point-75
- // Retrieve `then` only once
- then = returned &&
-
- // Support: Promises/A+ section 2.3.4
- // https://promisesaplus.com/#point-64
- // Only check objects and functions for thenability
- ( typeof returned === "object" ||
- typeof returned === "function" ) &&
- returned.then;
-
- // Handle a returned thenable
- if ( isFunction( then ) ) {
-
- // Special processors (notify) just wait for resolution
- if ( special ) {
- then.call(
- returned,
- resolve( maxDepth, deferred, Identity, special ),
- resolve( maxDepth, deferred, Thrower, special )
- );
-
- // Normal processors (resolve) also hook into progress
- } else {
-
- // ...and disregard older resolution values
- maxDepth++;
-
- then.call(
- returned,
- resolve( maxDepth, deferred, Identity, special ),
- resolve( maxDepth, deferred, Thrower, special ),
- resolve( maxDepth, deferred, Identity,
- deferred.notifyWith )
- );
- }
-
- // Handle all other returned values
- } else {
-
- // Only substitute handlers pass on context
- // and multiple values (non-spec behavior)
- if ( handler !== Identity ) {
- that = undefined;
- args = [ returned ];
- }
-
- // Process the value(s)
- // Default process is resolve
- ( special || deferred.resolveWith )( that, args );
- }
- },
-
- // Only normal processors (resolve) catch and reject exceptions
- process = special ?
- mightThrow :
- function() {
- try {
- mightThrow();
- } catch ( e ) {
-
- if ( jQuery.Deferred.exceptionHook ) {
- jQuery.Deferred.exceptionHook( e,
- process.error );
- }
-
- // Support: Promises/A+ section 2.3.3.3.4.1
- // https://promisesaplus.com/#point-61
- // Ignore post-resolution exceptions
- if ( depth + 1 >= maxDepth ) {
-
- // Only substitute handlers pass on context
- // and multiple values (non-spec behavior)
- if ( handler !== Thrower ) {
- that = undefined;
- args = [ e ];
- }
-
- deferred.rejectWith( that, args );
- }
- }
- };
-
- // Support: Promises/A+ section 2.3.3.3.1
- // https://promisesaplus.com/#point-57
- // Re-resolve promises immediately to dodge false rejection from
- // subsequent errors
- if ( depth ) {
- process();
- } else {
-
- // Call an optional hook to record the error, in case of exception
- // since it's otherwise lost when execution goes async
- if ( jQuery.Deferred.getErrorHook ) {
- process.error = jQuery.Deferred.getErrorHook();
-
- // The deprecated alias of the above. While the name suggests
- // returning the stack, not an error instance, jQuery just passes
- // it directly to `console.warn` so both will work; an instance
- // just better cooperates with source maps.
- } else if ( jQuery.Deferred.getStackHook ) {
- process.error = jQuery.Deferred.getStackHook();
- }
- window.setTimeout( process );
- }
- };
- }
-
- return jQuery.Deferred( function( newDefer ) {
-
- // progress_handlers.add( ... )
- tuples[ 0 ][ 3 ].add(
- resolve(
- 0,
- newDefer,
- isFunction( onProgress ) ?
- onProgress :
- Identity,
- newDefer.notifyWith
- )
- );
-
- // fulfilled_handlers.add( ... )
- tuples[ 1 ][ 3 ].add(
- resolve(
- 0,
- newDefer,
- isFunction( onFulfilled ) ?
- onFulfilled :
- Identity
- )
- );
-
- // rejected_handlers.add( ... )
- tuples[ 2 ][ 3 ].add(
- resolve(
- 0,
- newDefer,
- isFunction( onRejected ) ?
- onRejected :
- Thrower
- )
- );
- } ).promise();
- },
-
- // Get a promise for this deferred
- // If obj is provided, the promise aspect is added to the object
- promise: function( obj ) {
- return obj != null ? jQuery.extend( obj, promise ) : promise;
- }
- },
- deferred = {};
-
- // Add list-specific methods
- jQuery.each( tuples, function( i, tuple ) {
- var list = tuple[ 2 ],
- stateString = tuple[ 5 ];
-
- // promise.progress = list.add
- // promise.done = list.add
- // promise.fail = list.add
- promise[ tuple[ 1 ] ] = list.add;
-
- // Handle state
- if ( stateString ) {
- list.add(
- function() {
-
- // state = "resolved" (i.e., fulfilled)
- // state = "rejected"
- state = stateString;
- },
-
- // rejected_callbacks.disable
- // fulfilled_callbacks.disable
- tuples[ 3 - i ][ 2 ].disable,
-
- // rejected_handlers.disable
- // fulfilled_handlers.disable
- tuples[ 3 - i ][ 3 ].disable,
-
- // progress_callbacks.lock
- tuples[ 0 ][ 2 ].lock,
-
- // progress_handlers.lock
- tuples[ 0 ][ 3 ].lock
- );
- }
-
- // progress_handlers.fire
- // fulfilled_handlers.fire
- // rejected_handlers.fire
- list.add( tuple[ 3 ].fire );
-
- // deferred.notify = function() { deferred.notifyWith(...) }
- // deferred.resolve = function() { deferred.resolveWith(...) }
- // deferred.reject = function() { deferred.rejectWith(...) }
- deferred[ tuple[ 0 ] ] = function() {
- deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments );
- return this;
- };
-
- // deferred.notifyWith = list.fireWith
- // deferred.resolveWith = list.fireWith
- // deferred.rejectWith = list.fireWith
- deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
- } );
-
- // Make the deferred a promise
- promise.promise( deferred );
-
- // Call given func if any
- if ( func ) {
- func.call( deferred, deferred );
- }
-
- // All done!
- return deferred;
- },
-
- // Deferred helper
- when: function( singleValue ) {
- var
-
- // count of uncompleted subordinates
- remaining = arguments.length,
-
- // count of unprocessed arguments
- i = remaining,
-
- // subordinate fulfillment data
- resolveContexts = Array( i ),
- resolveValues = slice.call( arguments ),
-
- // the primary Deferred
- primary = jQuery.Deferred(),
-
- // subordinate callback factory
- updateFunc = function( i ) {
- return function( value ) {
- resolveContexts[ i ] = this;
- resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
- if ( !( --remaining ) ) {
- primary.resolveWith( resolveContexts, resolveValues );
- }
- };
- };
-
- // Single- and empty arguments are adopted like Promise.resolve
- if ( remaining <= 1 ) {
- adoptValue( singleValue, primary.done( updateFunc( i ) ).resolve, primary.reject,
- !remaining );
-
- // Use .then() to unwrap secondary thenables (cf. gh-3000)
- if ( primary.state() === "pending" ||
- isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {
-
- return primary.then();
- }
- }
-
- // Multiple arguments are aggregated like Promise.all array elements
- while ( i-- ) {
- adoptValue( resolveValues[ i ], updateFunc( i ), primary.reject );
- }
-
- return primary.promise();
- }
-} );
-
-
-// These usually indicate a programmer mistake during development,
-// warn about them ASAP rather than swallowing them by default.
-var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;
-
-// If `jQuery.Deferred.getErrorHook` is defined, `asyncError` is an error
-// captured before the async barrier to get the original error cause
-// which may otherwise be hidden.
-jQuery.Deferred.exceptionHook = function( error, asyncError ) {
-
- // Support: IE 8 - 9 only
- // Console exists when dev tools are open, which can happen at any time
- if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) {
- window.console.warn( "jQuery.Deferred exception: " + error.message,
- error.stack, asyncError );
- }
-};
-
-
-
-
-jQuery.readyException = function( error ) {
- window.setTimeout( function() {
- throw error;
- } );
-};
-
-
-
-
-// The deferred used on DOM ready
-var readyList = jQuery.Deferred();
-
-jQuery.fn.ready = function( fn ) {
-
- readyList
- .then( fn )
-
- // Wrap jQuery.readyException in a function so that the lookup
- // happens at the time of error handling instead of callback
- // registration.
- .catch( function( error ) {
- jQuery.readyException( error );
- } );
-
- return this;
-};
-
-jQuery.extend( {
-
- // Is the DOM ready to be used? Set to true once it occurs.
- isReady: false,
-
- // A counter to track how many items to wait for before
- // the ready event fires. See trac-6781
- readyWait: 1,
-
- // Handle when the DOM is ready
- ready: function( wait ) {
-
- // Abort if there are pending holds or we're already ready
- if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
- return;
- }
-
- // Remember that the DOM is ready
- jQuery.isReady = true;
-
- // If a normal DOM Ready event fired, decrement, and wait if need be
- if ( wait !== true && --jQuery.readyWait > 0 ) {
- return;
- }
-
- // If there are functions bound, to execute
- readyList.resolveWith( document, [ jQuery ] );
- }
-} );
-
-jQuery.ready.then = readyList.then;
-
-// The ready event handler and self cleanup method
-function completed() {
- document.removeEventListener( "DOMContentLoaded", completed );
- window.removeEventListener( "load", completed );
- jQuery.ready();
-}
-
-// Catch cases where $(document).ready() is called
-// after the browser event has already occurred.
-// Support: IE <=9 - 10 only
-// Older IE sometimes signals "interactive" too soon
-if ( document.readyState === "complete" ||
- ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {
-
- // Handle it asynchronously to allow scripts the opportunity to delay ready
- window.setTimeout( jQuery.ready );
-
-} else {
-
- // Use the handy event callback
- document.addEventListener( "DOMContentLoaded", completed );
-
- // A fallback to window.onload, that will always work
- window.addEventListener( "load", completed );
-}
-
-
-
-
-// Multifunctional method to get and set values of a collection
-// The value/s can optionally be executed if it's a function
-var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
- var i = 0,
- len = elems.length,
- bulk = key == null;
-
- // Sets many values
- if ( toType( key ) === "object" ) {
- chainable = true;
- for ( i in key ) {
- access( elems, fn, i, key[ i ], true, emptyGet, raw );
- }
-
- // Sets one value
- } else if ( value !== undefined ) {
- chainable = true;
-
- if ( !isFunction( value ) ) {
- raw = true;
- }
-
- if ( bulk ) {
-
- // Bulk operations run against the entire set
- if ( raw ) {
- fn.call( elems, value );
- fn = null;
-
- // ...except when executing function values
- } else {
- bulk = fn;
- fn = function( elem, _key, value ) {
- return bulk.call( jQuery( elem ), value );
- };
- }
- }
-
- if ( fn ) {
- for ( ; i < len; i++ ) {
- fn(
- elems[ i ], key, raw ?
- value :
- value.call( elems[ i ], i, fn( elems[ i ], key ) )
- );
- }
- }
- }
-
- if ( chainable ) {
- return elems;
- }
-
- // Gets
- if ( bulk ) {
- return fn.call( elems );
- }
-
- return len ? fn( elems[ 0 ], key ) : emptyGet;
-};
-
-
-// Matches dashed string for camelizing
-var rmsPrefix = /^-ms-/,
- rdashAlpha = /-([a-z])/g;
-
-// Used by camelCase as callback to replace()
-function fcamelCase( _all, letter ) {
- return letter.toUpperCase();
-}
-
-// Convert dashed to camelCase; used by the css and data modules
-// Support: IE <=9 - 11, Edge 12 - 15
-// Microsoft forgot to hump their vendor prefix (trac-9572)
-function camelCase( string ) {
- return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
-}
-var acceptData = function( owner ) {
-
- // Accepts only:
- // - Node
- // - Node.ELEMENT_NODE
- // - Node.DOCUMENT_NODE
- // - Object
- // - Any
- return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
-};
-
-
-
-
-function Data() {
- this.expando = jQuery.expando + Data.uid++;
-}
-
-Data.uid = 1;
-
-Data.prototype = {
-
- cache: function( owner ) {
-
- // Check if the owner object already has a cache
- var value = owner[ this.expando ];
-
- // If not, create one
- if ( !value ) {
- value = {};
-
- // We can accept data for non-element nodes in modern browsers,
- // but we should not, see trac-8335.
- // Always return an empty object.
- if ( acceptData( owner ) ) {
-
- // If it is a node unlikely to be stringify-ed or looped over
- // use plain assignment
- if ( owner.nodeType ) {
- owner[ this.expando ] = value;
-
- // Otherwise secure it in a non-enumerable property
- // configurable must be true to allow the property to be
- // deleted when data is removed
- } else {
- Object.defineProperty( owner, this.expando, {
- value: value,
- configurable: true
- } );
- }
- }
- }
-
- return value;
- },
- set: function( owner, data, value ) {
- var prop,
- cache = this.cache( owner );
-
- // Handle: [ owner, key, value ] args
- // Always use camelCase key (gh-2257)
- if ( typeof data === "string" ) {
- cache[ camelCase( data ) ] = value;
-
- // Handle: [ owner, { properties } ] args
- } else {
-
- // Copy the properties one-by-one to the cache object
- for ( prop in data ) {
- cache[ camelCase( prop ) ] = data[ prop ];
- }
- }
- return cache;
- },
- get: function( owner, key ) {
- return key === undefined ?
- this.cache( owner ) :
-
- // Always use camelCase key (gh-2257)
- owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ];
- },
- access: function( owner, key, value ) {
-
- // In cases where either:
- //
- // 1. No key was specified
- // 2. A string key was specified, but no value provided
- //
- // Take the "read" path and allow the get method to determine
- // which value to return, respectively either:
- //
- // 1. The entire cache object
- // 2. The data stored at the key
- //
- if ( key === undefined ||
- ( ( key && typeof key === "string" ) && value === undefined ) ) {
-
- return this.get( owner, key );
- }
-
- // When the key is not a string, or both a key and value
- // are specified, set or extend (existing objects) with either:
- //
- // 1. An object of properties
- // 2. A key and value
- //
- this.set( owner, key, value );
-
- // Since the "set" path can have two possible entry points
- // return the expected data based on which path was taken[*]
- return value !== undefined ? value : key;
- },
- remove: function( owner, key ) {
- var i,
- cache = owner[ this.expando ];
-
- if ( cache === undefined ) {
- return;
- }
-
- if ( key !== undefined ) {
-
- // Support array or space separated string of keys
- if ( Array.isArray( key ) ) {
-
- // If key is an array of keys...
- // We always set camelCase keys, so remove that.
- key = key.map( camelCase );
- } else {
- key = camelCase( key );
-
- // If a key with the spaces exists, use it.
- // Otherwise, create an array by matching non-whitespace
- key = key in cache ?
- [ key ] :
- ( key.match( rnothtmlwhite ) || [] );
- }
-
- i = key.length;
-
- while ( i-- ) {
- delete cache[ key[ i ] ];
- }
- }
-
- // Remove the expando if there's no more data
- if ( key === undefined || jQuery.isEmptyObject( cache ) ) {
-
- // Support: Chrome <=35 - 45
- // Webkit & Blink performance suffers when deleting properties
- // from DOM nodes, so set to undefined instead
- // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)
- if ( owner.nodeType ) {
- owner[ this.expando ] = undefined;
- } else {
- delete owner[ this.expando ];
- }
- }
- },
- hasData: function( owner ) {
- var cache = owner[ this.expando ];
- return cache !== undefined && !jQuery.isEmptyObject( cache );
- }
-};
-var dataPriv = new Data();
-
-var dataUser = new Data();
-
-
-
-// Implementation Summary
-//
-// 1. Enforce API surface and semantic compatibility with 1.9.x branch
-// 2. Improve the module's maintainability by reducing the storage
-// paths to a single mechanism.
-// 3. Use the same single mechanism to support "private" and "user" data.
-// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
-// 5. Avoid exposing implementation details on user objects (eg. expando properties)
-// 6. Provide a clear path for implementation upgrade to WeakMap in 2014
-
-var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
- rmultiDash = /[A-Z]/g;
-
-function getData( data ) {
- if ( data === "true" ) {
- return true;
- }
-
- if ( data === "false" ) {
- return false;
- }
-
- if ( data === "null" ) {
- return null;
- }
-
- // Only convert to a number if it doesn't change the string
- if ( data === +data + "" ) {
- return +data;
- }
-
- if ( rbrace.test( data ) ) {
- return JSON.parse( data );
- }
-
- return data;
-}
-
-function dataAttr( elem, key, data ) {
- var name;
-
- // If nothing was found internally, try to fetch any
- // data from the HTML5 data-* attribute
- if ( data === undefined && elem.nodeType === 1 ) {
- name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase();
- data = elem.getAttribute( name );
-
- if ( typeof data === "string" ) {
- try {
- data = getData( data );
- } catch ( e ) {}
-
- // Make sure we set the data so it isn't changed later
- dataUser.set( elem, key, data );
- } else {
- data = undefined;
- }
- }
- return data;
-}
-
-jQuery.extend( {
- hasData: function( elem ) {
- return dataUser.hasData( elem ) || dataPriv.hasData( elem );
- },
-
- data: function( elem, name, data ) {
- return dataUser.access( elem, name, data );
- },
-
- removeData: function( elem, name ) {
- dataUser.remove( elem, name );
- },
-
- // TODO: Now that all calls to _data and _removeData have been replaced
- // with direct calls to dataPriv methods, these can be deprecated.
- _data: function( elem, name, data ) {
- return dataPriv.access( elem, name, data );
- },
-
- _removeData: function( elem, name ) {
- dataPriv.remove( elem, name );
- }
-} );
-
-jQuery.fn.extend( {
- data: function( key, value ) {
- var i, name, data,
- elem = this[ 0 ],
- attrs = elem && elem.attributes;
-
- // Gets all values
- if ( key === undefined ) {
- if ( this.length ) {
- data = dataUser.get( elem );
-
- if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
- i = attrs.length;
- while ( i-- ) {
-
- // Support: IE 11 only
- // The attrs elements can be null (trac-14894)
- if ( attrs[ i ] ) {
- name = attrs[ i ].name;
- if ( name.indexOf( "data-" ) === 0 ) {
- name = camelCase( name.slice( 5 ) );
- dataAttr( elem, name, data[ name ] );
- }
- }
- }
- dataPriv.set( elem, "hasDataAttrs", true );
- }
- }
-
- return data;
- }
-
- // Sets multiple values
- if ( typeof key === "object" ) {
- return this.each( function() {
- dataUser.set( this, key );
- } );
- }
-
- return access( this, function( value ) {
- var data;
-
- // The calling jQuery object (element matches) is not empty
- // (and therefore has an element appears at this[ 0 ]) and the
- // `value` parameter was not undefined. An empty jQuery object
- // will result in `undefined` for elem = this[ 0 ] which will
- // throw an exception if an attempt to read a data cache is made.
- if ( elem && value === undefined ) {
-
- // Attempt to get data from the cache
- // The key will always be camelCased in Data
- data = dataUser.get( elem, key );
- if ( data !== undefined ) {
- return data;
- }
-
- // Attempt to "discover" the data in
- // HTML5 custom data-* attrs
- data = dataAttr( elem, key );
- if ( data !== undefined ) {
- return data;
- }
-
- // We tried really hard, but the data doesn't exist.
- return;
- }
-
- // Set the data...
- this.each( function() {
-
- // We always store the camelCased key
- dataUser.set( this, key, value );
- } );
- }, null, value, arguments.length > 1, null, true );
- },
-
- removeData: function( key ) {
- return this.each( function() {
- dataUser.remove( this, key );
- } );
- }
-} );
-
-
-jQuery.extend( {
- queue: function( elem, type, data ) {
- var queue;
-
- if ( elem ) {
- type = ( type || "fx" ) + "queue";
- queue = dataPriv.get( elem, type );
-
- // Speed up dequeue by getting out quickly if this is just a lookup
- if ( data ) {
- if ( !queue || Array.isArray( data ) ) {
- queue = dataPriv.access( elem, type, jQuery.makeArray( data ) );
- } else {
- queue.push( data );
- }
- }
- return queue || [];
- }
- },
-
- dequeue: function( elem, type ) {
- type = type || "fx";
-
- var queue = jQuery.queue( elem, type ),
- startLength = queue.length,
- fn = queue.shift(),
- hooks = jQuery._queueHooks( elem, type ),
- next = function() {
- jQuery.dequeue( elem, type );
- };
-
- // If the fx queue is dequeued, always remove the progress sentinel
- if ( fn === "inprogress" ) {
- fn = queue.shift();
- startLength--;
- }
-
- if ( fn ) {
-
- // Add a progress sentinel to prevent the fx queue from being
- // automatically dequeued
- if ( type === "fx" ) {
- queue.unshift( "inprogress" );
- }
-
- // Clear up the last queue stop function
- delete hooks.stop;
- fn.call( elem, next, hooks );
- }
-
- if ( !startLength && hooks ) {
- hooks.empty.fire();
- }
- },
-
- // Not public - generate a queueHooks object, or return the current one
- _queueHooks: function( elem, type ) {
- var key = type + "queueHooks";
- return dataPriv.get( elem, key ) || dataPriv.access( elem, key, {
- empty: jQuery.Callbacks( "once memory" ).add( function() {
- dataPriv.remove( elem, [ type + "queue", key ] );
- } )
- } );
- }
-} );
-
-jQuery.fn.extend( {
- queue: function( type, data ) {
- var setter = 2;
-
- if ( typeof type !== "string" ) {
- data = type;
- type = "fx";
- setter--;
- }
-
- if ( arguments.length < setter ) {
- return jQuery.queue( this[ 0 ], type );
- }
-
- return data === undefined ?
- this :
- this.each( function() {
- var queue = jQuery.queue( this, type, data );
-
- // Ensure a hooks for this queue
- jQuery._queueHooks( this, type );
-
- if ( type === "fx" && queue[ 0 ] !== "inprogress" ) {
- jQuery.dequeue( this, type );
- }
- } );
- },
- dequeue: function( type ) {
- return this.each( function() {
- jQuery.dequeue( this, type );
- } );
- },
- clearQueue: function( type ) {
- return this.queue( type || "fx", [] );
- },
-
- // Get a promise resolved when queues of a certain type
- // are emptied (fx is the type by default)
- promise: function( type, obj ) {
- var tmp,
- count = 1,
- defer = jQuery.Deferred(),
- elements = this,
- i = this.length,
- resolve = function() {
- if ( !( --count ) ) {
- defer.resolveWith( elements, [ elements ] );
- }
- };
-
- if ( typeof type !== "string" ) {
- obj = type;
- type = undefined;
- }
- type = type || "fx";
-
- while ( i-- ) {
- tmp = dataPriv.get( elements[ i ], type + "queueHooks" );
- if ( tmp && tmp.empty ) {
- count++;
- tmp.empty.add( resolve );
- }
- }
- resolve();
- return defer.promise( obj );
- }
-} );
-var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;
-
-var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" );
-
-
-var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
-
-var documentElement = document.documentElement;
-
-
-
- var isAttached = function( elem ) {
- return jQuery.contains( elem.ownerDocument, elem );
- },
- composed = { composed: true };
-
- // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only
- // Check attachment across shadow DOM boundaries when possible (gh-3504)
- // Support: iOS 10.0-10.2 only
- // Early iOS 10 versions support `attachShadow` but not `getRootNode`,
- // leading to errors. We need to check for `getRootNode`.
- if ( documentElement.getRootNode ) {
- isAttached = function( elem ) {
- return jQuery.contains( elem.ownerDocument, elem ) ||
- elem.getRootNode( composed ) === elem.ownerDocument;
- };
- }
-var isHiddenWithinTree = function( elem, el ) {
-
- // isHiddenWithinTree might be called from jQuery#filter function;
- // in that case, element will be second argument
- elem = el || elem;
-
- // Inline style trumps all
- return elem.style.display === "none" ||
- elem.style.display === "" &&
-
- // Otherwise, check computed style
- // Support: Firefox <=43 - 45
- // Disconnected elements can have computed display: none, so first confirm that elem is
- // in the document.
- isAttached( elem ) &&
-
- jQuery.css( elem, "display" ) === "none";
- };
-
-
-
-function adjustCSS( elem, prop, valueParts, tween ) {
- var adjusted, scale,
- maxIterations = 20,
- currentValue = tween ?
- function() {
- return tween.cur();
- } :
- function() {
- return jQuery.css( elem, prop, "" );
- },
- initial = currentValue(),
- unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
-
- // Starting value computation is required for potential unit mismatches
- initialInUnit = elem.nodeType &&
- ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) &&
- rcssNum.exec( jQuery.css( elem, prop ) );
-
- if ( initialInUnit && initialInUnit[ 3 ] !== unit ) {
-
- // Support: Firefox <=54
- // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144)
- initial = initial / 2;
-
- // Trust units reported by jQuery.css
- unit = unit || initialInUnit[ 3 ];
-
- // Iteratively approximate from a nonzero starting point
- initialInUnit = +initial || 1;
-
- while ( maxIterations-- ) {
-
- // Evaluate and update our best guess (doubling guesses that zero out).
- // Finish if the scale equals or crosses 1 (making the old*new product non-positive).
- jQuery.style( elem, prop, initialInUnit + unit );
- if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) {
- maxIterations = 0;
- }
- initialInUnit = initialInUnit / scale;
-
- }
-
- initialInUnit = initialInUnit * 2;
- jQuery.style( elem, prop, initialInUnit + unit );
-
- // Make sure we update the tween properties later on
- valueParts = valueParts || [];
- }
-
- if ( valueParts ) {
- initialInUnit = +initialInUnit || +initial || 0;
-
- // Apply relative offset (+=/-=) if specified
- adjusted = valueParts[ 1 ] ?
- initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :
- +valueParts[ 2 ];
- if ( tween ) {
- tween.unit = unit;
- tween.start = initialInUnit;
- tween.end = adjusted;
- }
- }
- return adjusted;
-}
-
-
-var defaultDisplayMap = {};
-
-function getDefaultDisplay( elem ) {
- var temp,
- doc = elem.ownerDocument,
- nodeName = elem.nodeName,
- display = defaultDisplayMap[ nodeName ];
-
- if ( display ) {
- return display;
- }
-
- temp = doc.body.appendChild( doc.createElement( nodeName ) );
- display = jQuery.css( temp, "display" );
-
- temp.parentNode.removeChild( temp );
-
- if ( display === "none" ) {
- display = "block";
- }
- defaultDisplayMap[ nodeName ] = display;
-
- return display;
-}
-
-function showHide( elements, show ) {
- var display, elem,
- values = [],
- index = 0,
- length = elements.length;
-
- // Determine new display value for elements that need to change
- for ( ; index < length; index++ ) {
- elem = elements[ index ];
- if ( !elem.style ) {
- continue;
- }
-
- display = elem.style.display;
- if ( show ) {
-
- // Since we force visibility upon cascade-hidden elements, an immediate (and slow)
- // check is required in this first loop unless we have a nonempty display value (either
- // inline or about-to-be-restored)
- if ( display === "none" ) {
- values[ index ] = dataPriv.get( elem, "display" ) || null;
- if ( !values[ index ] ) {
- elem.style.display = "";
- }
- }
- if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) {
- values[ index ] = getDefaultDisplay( elem );
- }
- } else {
- if ( display !== "none" ) {
- values[ index ] = "none";
-
- // Remember what we're overwriting
- dataPriv.set( elem, "display", display );
- }
- }
- }
-
- // Set the display of the elements in a second loop to avoid constant reflow
- for ( index = 0; index < length; index++ ) {
- if ( values[ index ] != null ) {
- elements[ index ].style.display = values[ index ];
- }
- }
-
- return elements;
-}
-
-jQuery.fn.extend( {
- show: function() {
- return showHide( this, true );
- },
- hide: function() {
- return showHide( this );
- },
- toggle: function( state ) {
- if ( typeof state === "boolean" ) {
- return state ? this.show() : this.hide();
- }
-
- return this.each( function() {
- if ( isHiddenWithinTree( this ) ) {
- jQuery( this ).show();
- } else {
- jQuery( this ).hide();
- }
- } );
- }
-} );
-var rcheckableType = ( /^(?:checkbox|radio)$/i );
-
-var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i );
-
-var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i );
-
-
-
-( function() {
- var fragment = document.createDocumentFragment(),
- div = fragment.appendChild( document.createElement( "div" ) ),
- input = document.createElement( "input" );
-
- // Support: Android 4.0 - 4.3 only
- // Check state lost if the name is set (trac-11217)
- // Support: Windows Web Apps (WWA)
- // `name` and `type` must use .setAttribute for WWA (trac-14901)
- input.setAttribute( "type", "radio" );
- input.setAttribute( "checked", "checked" );
- input.setAttribute( "name", "t" );
-
- div.appendChild( input );
-
- // Support: Android <=4.1 only
- // Older WebKit doesn't clone checked state correctly in fragments
- support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
-
- // Support: IE <=11 only
- // Make sure textarea (and checkbox) defaultValue is properly cloned
- div.innerHTML = "<textarea>x</textarea>";
- support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
-
- // Support: IE <=9 only
- // IE <=9 replaces <option> tags with their contents when inserted outside of
- // the select element.
- div.innerHTML = "<option></option>";
- support.option = !!div.lastChild;
-} )();
-
-
-// We have to close these tags to support XHTML (trac-13200)
-var wrapMap = {
-
- // XHTML parsers do not magically insert elements in the
- // same way that tag soup parsers do. So we cannot shorten
- // this by omitting <tbody> or other required elements.
- thead: [ 1, "<table>", "</table>" ],
- col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
- tr: [ 2, "<table><tbody>", "</tbody></table>" ],
- td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
-
- _default: [ 0, "", "" ]
-};
-
-wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
-wrapMap.th = wrapMap.td;
-
-// Support: IE <=9 only
-if ( !support.option ) {
- wrapMap.optgroup = wrapMap.option = [ 1, "<select multiple='multiple'>", "</select>" ];
-}
-
-
-function getAll( context, tag ) {
-
- // Support: IE <=9 - 11 only
- // Use typeof to avoid zero-argument method invocation on host objects (trac-15151)
- var ret;
-
- if ( typeof context.getElementsByTagName !== "undefined" ) {
- ret = context.getElementsByTagName( tag || "*" );
-
- } else if ( typeof context.querySelectorAll !== "undefined" ) {
- ret = context.querySelectorAll( tag || "*" );
-
- } else {
- ret = [];
- }
-
- if ( tag === undefined || tag && nodeName( context, tag ) ) {
- return jQuery.merge( [ context ], ret );
- }
-
- return ret;
-}
-
-
-// Mark scripts as having already been evaluated
-function setGlobalEval( elems, refElements ) {
- var i = 0,
- l = elems.length;
-
- for ( ; i < l; i++ ) {
- dataPriv.set(
- elems[ i ],
- "globalEval",
- !refElements || dataPriv.get( refElements[ i ], "globalEval" )
- );
- }
-}
-
-
-var rhtml = /<|&#?\w+;/;
-
-function buildFragment( elems, context, scripts, selection, ignored ) {
- var elem, tmp, tag, wrap, attached, j,
- fragment = context.createDocumentFragment(),
- nodes = [],
- i = 0,
- l = elems.length;
-
- for ( ; i < l; i++ ) {
- elem = elems[ i ];
-
- if ( elem || elem === 0 ) {
-
- // Add nodes directly
- if ( toType( elem ) === "object" ) {
-
- // Support: Android <=4.0 only, PhantomJS 1 only
- // push.apply(_, arraylike) throws on ancient WebKit
- jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
-
- // Convert non-html into a text node
- } else if ( !rhtml.test( elem ) ) {
- nodes.push( context.createTextNode( elem ) );
-
- // Convert html into DOM nodes
- } else {
- tmp = tmp || fragment.appendChild( context.createElement( "div" ) );
-
- // Deserialize a standard representation
- tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
- wrap = wrapMap[ tag ] || wrapMap._default;
- tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];
-
- // Descend through wrappers to the right content
- j = wrap[ 0 ];
- while ( j-- ) {
- tmp = tmp.lastChild;
- }
-
- // Support: Android <=4.0 only, PhantomJS 1 only
- // push.apply(_, arraylike) throws on ancient WebKit
- jQuery.merge( nodes, tmp.childNodes );
-
- // Remember the top-level container
- tmp = fragment.firstChild;
-
- // Ensure the created nodes are orphaned (trac-12392)
- tmp.textContent = "";
- }
- }
- }
-
- // Remove wrapper from fragment
- fragment.textContent = "";
-
- i = 0;
- while ( ( elem = nodes[ i++ ] ) ) {
-
- // Skip elements already in the context collection (trac-4087)
- if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
- if ( ignored ) {
- ignored.push( elem );
- }
- continue;
- }
-
- attached = isAttached( elem );
-
- // Append to fragment
- tmp = getAll( fragment.appendChild( elem ), "script" );
-
- // Preserve script evaluation history
- if ( attached ) {
- setGlobalEval( tmp );
- }
-
- // Capture executables
- if ( scripts ) {
- j = 0;
- while ( ( elem = tmp[ j++ ] ) ) {
- if ( rscriptType.test( elem.type || "" ) ) {
- scripts.push( elem );
- }
- }
- }
- }
-
- return fragment;
-}
-
-
-var rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
-
-function returnTrue() {
- return true;
-}
-
-function returnFalse() {
- return false;
-}
-
-function on( elem, types, selector, data, fn, one ) {
- var origFn, type;
-
- // Types can be a map of types/handlers
- if ( typeof types === "object" ) {
-
- // ( types-Object, selector, data )
- if ( typeof selector !== "string" ) {
-
- // ( types-Object, data )
- data = data || selector;
- selector = undefined;
- }
- for ( type in types ) {
- on( elem, type, selector, data, types[ type ], one );
- }
- return elem;
- }
-
- if ( data == null && fn == null ) {
-
- // ( types, fn )
- fn = selector;
- data = selector = undefined;
- } else if ( fn == null ) {
- if ( typeof selector === "string" ) {
-
- // ( types, selector, fn )
- fn = data;
- data = undefined;
- } else {
-
- // ( types, data, fn )
- fn = data;
- data = selector;
- selector = undefined;
- }
- }
- if ( fn === false ) {
- fn = returnFalse;
- } else if ( !fn ) {
- return elem;
- }
-
- if ( one === 1 ) {
- origFn = fn;
- fn = function( event ) {
-
- // Can use an empty set, since event contains the info
- jQuery().off( event );
- return origFn.apply( this, arguments );
- };
-
- // Use same guid so caller can remove using origFn
- fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
- }
- return elem.each( function() {
- jQuery.event.add( this, types, fn, data, selector );
- } );
-}
-
-/*
- * Helper functions for managing events -- not part of the public interface.
- * Props to Dean Edwards' addEvent library for many of the ideas.
- */
-jQuery.event = {
-
- global: {},
-
- add: function( elem, types, handler, data, selector ) {
-
- var handleObjIn, eventHandle, tmp,
- events, t, handleObj,
- special, handlers, type, namespaces, origType,
- elemData = dataPriv.get( elem );
-
- // Only attach events to objects that accept data
- if ( !acceptData( elem ) ) {
- return;
- }
-
- // Caller can pass in an object of custom data in lieu of the handler
- if ( handler.handler ) {
- handleObjIn = handler;
- handler = handleObjIn.handler;
- selector = handleObjIn.selector;
- }
-
- // Ensure that invalid selectors throw exceptions at attach time
- // Evaluate against documentElement in case elem is a non-element node (e.g., document)
- if ( selector ) {
- jQuery.find.matchesSelector( documentElement, selector );
- }
-
- // Make sure that the handler has a unique ID, used to find/remove it later
- if ( !handler.guid ) {
- handler.guid = jQuery.guid++;
- }
-
- // Init the element's event structure and main handler, if this is the first
- if ( !( events = elemData.events ) ) {
- events = elemData.events = Object.create( null );
- }
- if ( !( eventHandle = elemData.handle ) ) {
- eventHandle = elemData.handle = function( e ) {
-
- // Discard the second event of a jQuery.event.trigger() and
- // when an event is called after a page has unloaded
- return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
- jQuery.event.dispatch.apply( elem, arguments ) : undefined;
- };
- }
-
- // Handle multiple events separated by a space
- types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
- t = types.length;
- while ( t-- ) {
- tmp = rtypenamespace.exec( types[ t ] ) || [];
- type = origType = tmp[ 1 ];
- namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
-
- // There *must* be a type, no attaching namespace-only handlers
- if ( !type ) {
- continue;
- }
-
- // If event changes its type, use the special event handlers for the changed type
- special = jQuery.event.special[ type ] || {};
-
- // If selector defined, determine special event api type, otherwise given type
- type = ( selector ? special.delegateType : special.bindType ) || type;
-
- // Update special based on newly reset type
- special = jQuery.event.special[ type ] || {};
-
- // handleObj is passed to all event handlers
- handleObj = jQuery.extend( {
- type: type,
- origType: origType,
- data: data,
- handler: handler,
- guid: handler.guid,
- selector: selector,
- needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
- namespace: namespaces.join( "." )
- }, handleObjIn );
-
- // Init the event handler queue if we're the first
- if ( !( handlers = events[ type ] ) ) {
- handlers = events[ type ] = [];
- handlers.delegateCount = 0;
-
- // Only use addEventListener if the special events handler returns false
- if ( !special.setup ||
- special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
-
- if ( elem.addEventListener ) {
- elem.addEventListener( type, eventHandle );
- }
- }
- }
-
- if ( special.add ) {
- special.add.call( elem, handleObj );
-
- if ( !handleObj.handler.guid ) {
- handleObj.handler.guid = handler.guid;
- }
- }
-
- // Add to the element's handler list, delegates in front
- if ( selector ) {
- handlers.splice( handlers.delegateCount++, 0, handleObj );
- } else {
- handlers.push( handleObj );
- }
-
- // Keep track of which events have ever been used, for event optimization
- jQuery.event.global[ type ] = true;
- }
-
- },
-
- // Detach an event or set of events from an element
- remove: function( elem, types, handler, selector, mappedTypes ) {
-
- var j, origCount, tmp,
- events, t, handleObj,
- special, handlers, type, namespaces, origType,
- elemData = dataPriv.hasData( elem ) && dataPriv.get( elem );
-
- if ( !elemData || !( events = elemData.events ) ) {
- return;
- }
-
- // Once for each type.namespace in types; type may be omitted
- types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
- t = types.length;
- while ( t-- ) {
- tmp = rtypenamespace.exec( types[ t ] ) || [];
- type = origType = tmp[ 1 ];
- namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
-
- // Unbind all events (on this namespace, if provided) for the element
- if ( !type ) {
- for ( type in events ) {
- jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
- }
- continue;
- }
-
- special = jQuery.event.special[ type ] || {};
- type = ( selector ? special.delegateType : special.bindType ) || type;
- handlers = events[ type ] || [];
- tmp = tmp[ 2 ] &&
- new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" );
-
- // Remove matching events
- origCount = j = handlers.length;
- while ( j-- ) {
- handleObj = handlers[ j ];
-
- if ( ( mappedTypes || origType === handleObj.origType ) &&
- ( !handler || handler.guid === handleObj.guid ) &&
- ( !tmp || tmp.test( handleObj.namespace ) ) &&
- ( !selector || selector === handleObj.selector ||
- selector === "**" && handleObj.selector ) ) {
- handlers.splice( j, 1 );
-
- if ( handleObj.selector ) {
- handlers.delegateCount--;
- }
- if ( special.remove ) {
- special.remove.call( elem, handleObj );
- }
- }
- }
-
- // Remove generic event handler if we removed something and no more handlers exist
- // (avoids potential for endless recursion during removal of special event handlers)
- if ( origCount && !handlers.length ) {
- if ( !special.teardown ||
- special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
-
- jQuery.removeEvent( elem, type, elemData.handle );
- }
-
- delete events[ type ];
- }
- }
-
- // Remove data and the expando if it's no longer used
- if ( jQuery.isEmptyObject( events ) ) {
- dataPriv.remove( elem, "handle events" );
- }
- },
-
- dispatch: function( nativeEvent ) {
-
- var i, j, ret, matched, handleObj, handlerQueue,
- args = new Array( arguments.length ),
-
- // Make a writable jQuery.Event from the native event object
- event = jQuery.event.fix( nativeEvent ),
-
- handlers = (
- dataPriv.get( this, "events" ) || Object.create( null )
- )[ event.type ] || [],
- special = jQuery.event.special[ event.type ] || {};
-
- // Use the fix-ed jQuery.Event rather than the (read-only) native event
- args[ 0 ] = event;
-
- for ( i = 1; i < arguments.length; i++ ) {
- args[ i ] = arguments[ i ];
- }
-
- event.delegateTarget = this;
-
- // Call the preDispatch hook for the mapped type, and let it bail if desired
- if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
- return;
- }
-
- // Determine handlers
- handlerQueue = jQuery.event.handlers.call( this, event, handlers );
-
- // Run delegates first; they may want to stop propagation beneath us
- i = 0;
- while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
- event.currentTarget = matched.elem;
-
- j = 0;
- while ( ( handleObj = matched.handlers[ j++ ] ) &&
- !event.isImmediatePropagationStopped() ) {
-
- // If the event is namespaced, then each handler is only invoked if it is
- // specially universal or its namespaces are a superset of the event's.
- if ( !event.rnamespace || handleObj.namespace === false ||
- event.rnamespace.test( handleObj.namespace ) ) {
-
- event.handleObj = handleObj;
- event.data = handleObj.data;
-
- ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
- handleObj.handler ).apply( matched.elem, args );
-
- if ( ret !== undefined ) {
- if ( ( event.result = ret ) === false ) {
- event.preventDefault();
- event.stopPropagation();
- }
- }
- }
- }
- }
-
- // Call the postDispatch hook for the mapped type
- if ( special.postDispatch ) {
- special.postDispatch.call( this, event );
- }
-
- return event.result;
- },
-
- handlers: function( event, handlers ) {
- var i, handleObj, sel, matchedHandlers, matchedSelectors,
- handlerQueue = [],
- delegateCount = handlers.delegateCount,
- cur = event.target;
-
- // Find delegate handlers
- if ( delegateCount &&
-
- // Support: IE <=9
- // Black-hole SVG <use> instance trees (trac-13180)
- cur.nodeType &&
-
- // Support: Firefox <=42
- // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861)
- // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click
- // Support: IE 11 only
- // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343)
- !( event.type === "click" && event.button >= 1 ) ) {
-
- for ( ; cur !== this; cur = cur.parentNode || this ) {
-
- // Don't check non-elements (trac-13208)
- // Don't process clicks on disabled elements (trac-6911, trac-8165, trac-11382, trac-11764)
- if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) {
- matchedHandlers = [];
- matchedSelectors = {};
- for ( i = 0; i < delegateCount; i++ ) {
- handleObj = handlers[ i ];
-
- // Don't conflict with Object.prototype properties (trac-13203)
- sel = handleObj.selector + " ";
-
- if ( matchedSelectors[ sel ] === undefined ) {
- matchedSelectors[ sel ] = handleObj.needsContext ?
- jQuery( sel, this ).index( cur ) > -1 :
- jQuery.find( sel, this, null, [ cur ] ).length;
- }
- if ( matchedSelectors[ sel ] ) {
- matchedHandlers.push( handleObj );
- }
- }
- if ( matchedHandlers.length ) {
- handlerQueue.push( { elem: cur, handlers: matchedHandlers } );
- }
- }
- }
- }
-
- // Add the remaining (directly-bound) handlers
- cur = this;
- if ( delegateCount < handlers.length ) {
- handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } );
- }
-
- return handlerQueue;
- },
-
- addProp: function( name, hook ) {
- Object.defineProperty( jQuery.Event.prototype, name, {
- enumerable: true,
- configurable: true,
-
- get: isFunction( hook ) ?
- function() {
- if ( this.originalEvent ) {
- return hook( this.originalEvent );
- }
- } :
- function() {
- if ( this.originalEvent ) {
- return this.originalEvent[ name ];
- }
- },
-
- set: function( value ) {
- Object.defineProperty( this, name, {
- enumerable: true,
- configurable: true,
- writable: true,
- value: value
- } );
- }
- } );
- },
-
- fix: function( originalEvent ) {
- return originalEvent[ jQuery.expando ] ?
- originalEvent :
- new jQuery.Event( originalEvent );
- },
-
- special: {
- load: {
-
- // Prevent triggered image.load events from bubbling to window.load
- noBubble: true
- },
- click: {
-
- // Utilize native event to ensure correct state for checkable inputs
- setup: function( data ) {
-
- // For mutual compressibility with _default, replace `this` access with a local var.
- // `|| data` is dead code meant only to preserve the variable through minification.
- var el = this || data;
-
- // Claim the first handler
- if ( rcheckableType.test( el.type ) &&
- el.click && nodeName( el, "input" ) ) {
-
- // dataPriv.set( el, "click", ... )
- leverageNative( el, "click", true );
- }
-
- // Return false to allow normal processing in the caller
- return false;
- },
- trigger: function( data ) {
-
- // For mutual compressibility with _default, replace `this` access with a local var.
- // `|| data` is dead code meant only to preserve the variable through minification.
- var el = this || data;
-
- // Force setup before triggering a click
- if ( rcheckableType.test( el.type ) &&
- el.click && nodeName( el, "input" ) ) {
-
- leverageNative( el, "click" );
- }
-
- // Return non-false to allow normal event-path propagation
- return true;
- },
-
- // For cross-browser consistency, suppress native .click() on links
- // Also prevent it if we're currently inside a leveraged native-event stack
- _default: function( event ) {
- var target = event.target;
- return rcheckableType.test( target.type ) &&
- target.click && nodeName( target, "input" ) &&
- dataPriv.get( target, "click" ) ||
- nodeName( target, "a" );
- }
- },
-
- beforeunload: {
- postDispatch: function( event ) {
-
- // Support: Firefox 20+
- // Firefox doesn't alert if the returnValue field is not set.
- if ( event.result !== undefined && event.originalEvent ) {
- event.originalEvent.returnValue = event.result;
- }
- }
- }
- }
-};
-
-// Ensure the presence of an event listener that handles manually-triggered
-// synthetic events by interrupting progress until reinvoked in response to
-// *native* events that it fires directly, ensuring that state changes have
-// already occurred before other listeners are invoked.
-function leverageNative( el, type, isSetup ) {
-
- // Missing `isSetup` indicates a trigger call, which must force setup through jQuery.event.add
- if ( !isSetup ) {
- if ( dataPriv.get( el, type ) === undefined ) {
- jQuery.event.add( el, type, returnTrue );
- }
- return;
- }
-
- // Register the controller as a special universal handler for all event namespaces
- dataPriv.set( el, type, false );
- jQuery.event.add( el, type, {
- namespace: false,
- handler: function( event ) {
- var result,
- saved = dataPriv.get( this, type );
-
- if ( ( event.isTrigger & 1 ) && this[ type ] ) {
-
- // Interrupt processing of the outer synthetic .trigger()ed event
- if ( !saved ) {
-
- // Store arguments for use when handling the inner native event
- // There will always be at least one argument (an event object), so this array
- // will not be confused with a leftover capture object.
- saved = slice.call( arguments );
- dataPriv.set( this, type, saved );
-
- // Trigger the native event and capture its result
- this[ type ]();
- result = dataPriv.get( this, type );
- dataPriv.set( this, type, false );
-
- if ( saved !== result ) {
-
- // Cancel the outer synthetic event
- event.stopImmediatePropagation();
- event.preventDefault();
-
- return result;
- }
-
- // If this is an inner synthetic event for an event with a bubbling surrogate
- // (focus or blur), assume that the surrogate already propagated from triggering
- // the native event and prevent that from happening again here.
- // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the
- // bubbling surrogate propagates *after* the non-bubbling base), but that seems
- // less bad than duplication.
- } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) {
- event.stopPropagation();
- }
-
- // If this is a native event triggered above, everything is now in order
- // Fire an inner synthetic event with the original arguments
- } else if ( saved ) {
-
- // ...and capture the result
- dataPriv.set( this, type, jQuery.event.trigger(
- saved[ 0 ],
- saved.slice( 1 ),
- this
- ) );
-
- // Abort handling of the native event by all jQuery handlers while allowing
- // native handlers on the same element to run. On target, this is achieved
- // by stopping immediate propagation just on the jQuery event. However,
- // the native event is re-wrapped by a jQuery one on each level of the
- // propagation so the only way to stop it for jQuery is to stop it for
- // everyone via native `stopPropagation()`. This is not a problem for
- // focus/blur which don't bubble, but it does also stop click on checkboxes
- // and radios. We accept this limitation.
- event.stopPropagation();
- event.isImmediatePropagationStopped = returnTrue;
- }
- }
- } );
-}
-
-jQuery.removeEvent = function( elem, type, handle ) {
-
- // This "if" is needed for plain objects
- if ( elem.removeEventListener ) {
- elem.removeEventListener( type, handle );
- }
-};
-
-jQuery.Event = function( src, props ) {
-
- // Allow instantiation without the 'new' keyword
- if ( !( this instanceof jQuery.Event ) ) {
- return new jQuery.Event( src, props );
- }
-
- // Event object
- if ( src && src.type ) {
- this.originalEvent = src;
- this.type = src.type;
-
- // Events bubbling up the document may have been marked as prevented
- // by a handler lower down the tree; reflect the correct value.
- this.isDefaultPrevented = src.defaultPrevented ||
- src.defaultPrevented === undefined &&
-
- // Support: Android <=2.3 only
- src.returnValue === false ?
- returnTrue :
- returnFalse;
-
- // Create target properties
- // Support: Safari <=6 - 7 only
- // Target should not be a text node (trac-504, trac-13143)
- this.target = ( src.target && src.target.nodeType === 3 ) ?
- src.target.parentNode :
- src.target;
-
- this.currentTarget = src.currentTarget;
- this.relatedTarget = src.relatedTarget;
-
- // Event type
- } else {
- this.type = src;
- }
-
- // Put explicitly provided properties onto the event object
- if ( props ) {
- jQuery.extend( this, props );
- }
-
- // Create a timestamp if incoming event doesn't have one
- this.timeStamp = src && src.timeStamp || Date.now();
-
- // Mark it as fixed
- this[ jQuery.expando ] = true;
-};
-
-// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
-// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
-jQuery.Event.prototype = {
- constructor: jQuery.Event,
- isDefaultPrevented: returnFalse,
- isPropagationStopped: returnFalse,
- isImmediatePropagationStopped: returnFalse,
- isSimulated: false,
-
- preventDefault: function() {
- var e = this.originalEvent;
-
- this.isDefaultPrevented = returnTrue;
-
- if ( e && !this.isSimulated ) {
- e.preventDefault();
- }
- },
- stopPropagation: function() {
- var e = this.originalEvent;
-
- this.isPropagationStopped = returnTrue;
-
- if ( e && !this.isSimulated ) {
- e.stopPropagation();
- }
- },
- stopImmediatePropagation: function() {
- var e = this.originalEvent;
-
- this.isImmediatePropagationStopped = returnTrue;
-
- if ( e && !this.isSimulated ) {
- e.stopImmediatePropagation();
- }
-
- this.stopPropagation();
- }
-};
-
-// Includes all common event props including KeyEvent and MouseEvent specific props
-jQuery.each( {
- altKey: true,
- bubbles: true,
- cancelable: true,
- changedTouches: true,
- ctrlKey: true,
- detail: true,
- eventPhase: true,
- metaKey: true,
- pageX: true,
- pageY: true,
- shiftKey: true,
- view: true,
- "char": true,
- code: true,
- charCode: true,
- key: true,
- keyCode: true,
- button: true,
- buttons: true,
- clientX: true,
- clientY: true,
- offsetX: true,
- offsetY: true,
- pointerId: true,
- pointerType: true,
- screenX: true,
- screenY: true,
- targetTouches: true,
- toElement: true,
- touches: true,
- which: true
-}, jQuery.event.addProp );
-
-jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) {
-
- function focusMappedHandler( nativeEvent ) {
- if ( document.documentMode ) {
-
- // Support: IE 11+
- // Attach a single focusin/focusout handler on the document while someone wants
- // focus/blur. This is because the former are synchronous in IE while the latter
- // are async. In other browsers, all those handlers are invoked synchronously.
-
- // `handle` from private data would already wrap the event, but we need
- // to change the `type` here.
- var handle = dataPriv.get( this, "handle" ),
- event = jQuery.event.fix( nativeEvent );
- event.type = nativeEvent.type === "focusin" ? "focus" : "blur";
- event.isSimulated = true;
-
- // First, handle focusin/focusout
- handle( nativeEvent );
-
- // ...then, handle focus/blur
- //
- // focus/blur don't bubble while focusin/focusout do; simulate the former by only
- // invoking the handler at the lower level.
- if ( event.target === event.currentTarget ) {
-
- // The setup part calls `leverageNative`, which, in turn, calls
- // `jQuery.event.add`, so event handle will already have been set
- // by this point.
- handle( event );
- }
- } else {
-
- // For non-IE browsers, attach a single capturing handler on the document
- // while someone wants focusin/focusout.
- jQuery.event.simulate( delegateType, nativeEvent.target,
- jQuery.event.fix( nativeEvent ) );
- }
- }
-
- jQuery.event.special[ type ] = {
-
- // Utilize native event if possible so blur/focus sequence is correct
- setup: function() {
-
- var attaches;
-
- // Claim the first handler
- // dataPriv.set( this, "focus", ... )
- // dataPriv.set( this, "blur", ... )
- leverageNative( this, type, true );
-
- if ( document.documentMode ) {
-
- // Support: IE 9 - 11+
- // We use the same native handler for focusin & focus (and focusout & blur)
- // so we need to coordinate setup & teardown parts between those events.
- // Use `delegateType` as the key as `type` is already used by `leverageNative`.
- attaches = dataPriv.get( this, delegateType );
- if ( !attaches ) {
- this.addEventListener( delegateType, focusMappedHandler );
- }
- dataPriv.set( this, delegateType, ( attaches || 0 ) + 1 );
- } else {
-
- // Return false to allow normal processing in the caller
- return false;
- }
- },
- trigger: function() {
-
- // Force setup before trigger
- leverageNative( this, type );
-
- // Return non-false to allow normal event-path propagation
- return true;
- },
-
- teardown: function() {
- var attaches;
-
- if ( document.documentMode ) {
- attaches = dataPriv.get( this, delegateType ) - 1;
- if ( !attaches ) {
- this.removeEventListener( delegateType, focusMappedHandler );
- dataPriv.remove( this, delegateType );
- } else {
- dataPriv.set( this, delegateType, attaches );
- }
- } else {
-
- // Return false to indicate standard teardown should be applied
- return false;
- }
- },
-
- // Suppress native focus or blur if we're currently inside
- // a leveraged native-event stack
- _default: function( event ) {
- return dataPriv.get( event.target, type );
- },
-
- delegateType: delegateType
- };
-
- // Support: Firefox <=44
- // Firefox doesn't have focus(in | out) events
- // Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787
- //
- // Support: Chrome <=48 - 49, Safari <=9.0 - 9.1
- // focus(in | out) events fire after focus & blur events,
- // which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order
- // Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857
- //
- // Support: IE 9 - 11+
- // To preserve relative focusin/focus & focusout/blur event order guaranteed on the 3.x branch,
- // attach a single handler for both events in IE.
- jQuery.event.special[ delegateType ] = {
- setup: function() {
-
- // Handle: regular nodes (via `this.ownerDocument`), window
- // (via `this.document`) & document (via `this`).
- var doc = this.ownerDocument || this.document || this,
- dataHolder = document.documentMode ? this : doc,
- attaches = dataPriv.get( dataHolder, delegateType );
-
- // Support: IE 9 - 11+
- // We use the same native handler for focusin & focus (and focusout & blur)
- // so we need to coordinate setup & teardown parts between those events.
- // Use `delegateType` as the key as `type` is already used by `leverageNative`.
- if ( !attaches ) {
- if ( document.documentMode ) {
- this.addEventListener( delegateType, focusMappedHandler );
- } else {
- doc.addEventListener( type, focusMappedHandler, true );
- }
- }
- dataPriv.set( dataHolder, delegateType, ( attaches || 0 ) + 1 );
- },
- teardown: function() {
- var doc = this.ownerDocument || this.document || this,
- dataHolder = document.documentMode ? this : doc,
- attaches = dataPriv.get( dataHolder, delegateType ) - 1;
-
- if ( !attaches ) {
- if ( document.documentMode ) {
- this.removeEventListener( delegateType, focusMappedHandler );
- } else {
- doc.removeEventListener( type, focusMappedHandler, true );
- }
- dataPriv.remove( dataHolder, delegateType );
- } else {
- dataPriv.set( dataHolder, delegateType, attaches );
- }
- }
- };
-} );
-
-// Create mouseenter/leave events using mouseover/out and event-time checks
-// so that event delegation works in jQuery.
-// Do the same for pointerenter/pointerleave and pointerover/pointerout
-//
-// Support: Safari 7 only
-// Safari sends mouseenter too often; see:
-// https://bugs.chromium.org/p/chromium/issues/detail?id=470258
-// for the description of the bug (it existed in older Chrome versions as well).
-jQuery.each( {
- mouseenter: "mouseover",
- mouseleave: "mouseout",
- pointerenter: "pointerover",
- pointerleave: "pointerout"
-}, function( orig, fix ) {
- jQuery.event.special[ orig ] = {
- delegateType: fix,
- bindType: fix,
-
- handle: function( event ) {
- var ret,
- target = this,
- related = event.relatedTarget,
- handleObj = event.handleObj;
-
- // For mouseenter/leave call the handler if related is outside the target.
- // NB: No relatedTarget if the mouse left/entered the browser window
- if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) {
- event.type = handleObj.origType;
- ret = handleObj.handler.apply( this, arguments );
- event.type = fix;
- }
- return ret;
- }
- };
-} );
-
-jQuery.fn.extend( {
-
- on: function( types, selector, data, fn ) {
- return on( this, types, selector, data, fn );
- },
- one: function( types, selector, data, fn ) {
- return on( this, types, selector, data, fn, 1 );
- },
- off: function( types, selector, fn ) {
- var handleObj, type;
- if ( types && types.preventDefault && types.handleObj ) {
-
- // ( event ) dispatched jQuery.Event
- handleObj = types.handleObj;
- jQuery( types.delegateTarget ).off(
- handleObj.namespace ?
- handleObj.origType + "." + handleObj.namespace :
- handleObj.origType,
- handleObj.selector,
- handleObj.handler
- );
- return this;
- }
- if ( typeof types === "object" ) {
-
- // ( types-object [, selector] )
- for ( type in types ) {
- this.off( type, selector, types[ type ] );
- }
- return this;
- }
- if ( selector === false || typeof selector === "function" ) {
-
- // ( types [, fn] )
- fn = selector;
- selector = undefined;
- }
- if ( fn === false ) {
- fn = returnFalse;
- }
- return this.each( function() {
- jQuery.event.remove( this, types, fn, selector );
- } );
- }
-} );
-
-
-var
-
- // Support: IE <=10 - 11, Edge 12 - 13 only
- // In IE/Edge using regex groups here causes severe slowdowns.
- // See https://connect.microsoft.com/IE/feedback/details/1736512/
- rnoInnerhtml = /<script|<style|<link/i,
-
- // checked="checked" or checked
- rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
-
- rcleanScript = /^\s*<!\[CDATA\[|\]\]>\s*$/g;
-
-// Prefer a tbody over its parent table for containing new rows
-function manipulationTarget( elem, content ) {
- if ( nodeName( elem, "table" ) &&
- nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) {
-
- return jQuery( elem ).children( "tbody" )[ 0 ] || elem;
- }
-
- return elem;
-}
-
-// Replace/restore the type attribute of script elements for safe DOM manipulation
-function disableScript( elem ) {
- elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type;
- return elem;
-}
-function restoreScript( elem ) {
- if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) {
- elem.type = elem.type.slice( 5 );
- } else {
- elem.removeAttribute( "type" );
- }
-
- return elem;
-}
-
-function cloneCopyEvent( src, dest ) {
- var i, l, type, pdataOld, udataOld, udataCur, events;
-
- if ( dest.nodeType !== 1 ) {
- return;
- }
-
- // 1. Copy private data: events, handlers, etc.
- if ( dataPriv.hasData( src ) ) {
- pdataOld = dataPriv.get( src );
- events = pdataOld.events;
-
- if ( events ) {
- dataPriv.remove( dest, "handle events" );
-
- for ( type in events ) {
- for ( i = 0, l = events[ type ].length; i < l; i++ ) {
- jQuery.event.add( dest, type, events[ type ][ i ] );
- }
- }
- }
- }
-
- // 2. Copy user data
- if ( dataUser.hasData( src ) ) {
- udataOld = dataUser.access( src );
- udataCur = jQuery.extend( {}, udataOld );
-
- dataUser.set( dest, udataCur );
- }
-}
-
-// Fix IE bugs, see support tests
-function fixInput( src, dest ) {
- var nodeName = dest.nodeName.toLowerCase();
-
- // Fails to persist the checked state of a cloned checkbox or radio button.
- if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
- dest.checked = src.checked;
-
- // Fails to return the selected option to the default selected state when cloning options
- } else if ( nodeName === "input" || nodeName === "textarea" ) {
- dest.defaultValue = src.defaultValue;
- }
-}
-
-function domManip( collection, args, callback, ignored ) {
-
- // Flatten any nested arrays
- args = flat( args );
-
- var fragment, first, scripts, hasScripts, node, doc,
- i = 0,
- l = collection.length,
- iNoClone = l - 1,
- value = args[ 0 ],
- valueIsFunction = isFunction( value );
-
- // We can't cloneNode fragments that contain checked, in WebKit
- if ( valueIsFunction ||
- ( l > 1 && typeof value === "string" &&
- !support.checkClone && rchecked.test( value ) ) ) {
- return collection.each( function( index ) {
- var self = collection.eq( index );
- if ( valueIsFunction ) {
- args[ 0 ] = value.call( this, index, self.html() );
- }
- domManip( self, args, callback, ignored );
- } );
- }
-
- if ( l ) {
- fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored );
- first = fragment.firstChild;
-
- if ( fragment.childNodes.length === 1 ) {
- fragment = first;
- }
-
- // Require either new content or an interest in ignored elements to invoke the callback
- if ( first || ignored ) {
- scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
- hasScripts = scripts.length;
-
- // Use the original fragment for the last item
- // instead of the first because it can end up
- // being emptied incorrectly in certain situations (trac-8070).
- for ( ; i < l; i++ ) {
- node = fragment;
-
- if ( i !== iNoClone ) {
- node = jQuery.clone( node, true, true );
-
- // Keep references to cloned scripts for later restoration
- if ( hasScripts ) {
-
- // Support: Android <=4.0 only, PhantomJS 1 only
- // push.apply(_, arraylike) throws on ancient WebKit
- jQuery.merge( scripts, getAll( node, "script" ) );
- }
- }
-
- callback.call( collection[ i ], node, i );
- }
-
- if ( hasScripts ) {
- doc = scripts[ scripts.length - 1 ].ownerDocument;
-
- // Reenable scripts
- jQuery.map( scripts, restoreScript );
-
- // Evaluate executable scripts on first document insertion
- for ( i = 0; i < hasScripts; i++ ) {
- node = scripts[ i ];
- if ( rscriptType.test( node.type || "" ) &&
- !dataPriv.access( node, "globalEval" ) &&
- jQuery.contains( doc, node ) ) {
-
- if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) {
-
- // Optional AJAX dependency, but won't run scripts if not present
- if ( jQuery._evalUrl && !node.noModule ) {
- jQuery._evalUrl( node.src, {
- nonce: node.nonce || node.getAttribute( "nonce" )
- }, doc );
- }
- } else {
-
- // Unwrap a CDATA section containing script contents. This shouldn't be
- // needed as in XML documents they're already not visible when
- // inspecting element contents and in HTML documents they have no
- // meaning but we're preserving that logic for backwards compatibility.
- // This will be removed completely in 4.0. See gh-4904.
- DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc );
- }
- }
- }
- }
- }
- }
-
- return collection;
-}
-
-function remove( elem, selector, keepData ) {
- var node,
- nodes = selector ? jQuery.filter( selector, elem ) : elem,
- i = 0;
-
- for ( ; ( node = nodes[ i ] ) != null; i++ ) {
- if ( !keepData && node.nodeType === 1 ) {
- jQuery.cleanData( getAll( node ) );
- }
-
- if ( node.parentNode ) {
- if ( keepData && isAttached( node ) ) {
- setGlobalEval( getAll( node, "script" ) );
- }
- node.parentNode.removeChild( node );
- }
- }
-
- return elem;
-}
-
-jQuery.extend( {
- htmlPrefilter: function( html ) {
- return html;
- },
-
- clone: function( elem, dataAndEvents, deepDataAndEvents ) {
- var i, l, srcElements, destElements,
- clone = elem.cloneNode( true ),
- inPage = isAttached( elem );
-
- // Fix IE cloning issues
- if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&
- !jQuery.isXMLDoc( elem ) ) {
-
- // We eschew jQuery#find here for performance reasons:
- // https://jsperf.com/getall-vs-sizzle/2
- destElements = getAll( clone );
- srcElements = getAll( elem );
-
- for ( i = 0, l = srcElements.length; i < l; i++ ) {
- fixInput( srcElements[ i ], destElements[ i ] );
- }
- }
-
- // Copy the events from the original to the clone
- if ( dataAndEvents ) {
- if ( deepDataAndEvents ) {
- srcElements = srcElements || getAll( elem );
- destElements = destElements || getAll( clone );
-
- for ( i = 0, l = srcElements.length; i < l; i++ ) {
- cloneCopyEvent( srcElements[ i ], destElements[ i ] );
- }
- } else {
- cloneCopyEvent( elem, clone );
- }
- }
-
- // Preserve script evaluation history
- destElements = getAll( clone, "script" );
- if ( destElements.length > 0 ) {
- setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
- }
-
- // Return the cloned set
- return clone;
- },
-
- cleanData: function( elems ) {
- var data, elem, type,
- special = jQuery.event.special,
- i = 0;
-
- for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) {
- if ( acceptData( elem ) ) {
- if ( ( data = elem[ dataPriv.expando ] ) ) {
- if ( data.events ) {
- for ( type in data.events ) {
- if ( special[ type ] ) {
- jQuery.event.remove( elem, type );
-
- // This is a shortcut to avoid jQuery.event.remove's overhead
- } else {
- jQuery.removeEvent( elem, type, data.handle );
- }
- }
- }
-
- // Support: Chrome <=35 - 45+
- // Assign undefined instead of using delete, see Data#remove
- elem[ dataPriv.expando ] = undefined;
- }
- if ( elem[ dataUser.expando ] ) {
-
- // Support: Chrome <=35 - 45+
- // Assign undefined instead of using delete, see Data#remove
- elem[ dataUser.expando ] = undefined;
- }
- }
- }
- }
-} );
-
-jQuery.fn.extend( {
- detach: function( selector ) {
- return remove( this, selector, true );
- },
-
- remove: function( selector ) {
- return remove( this, selector );
- },
-
- text: function( value ) {
- return access( this, function( value ) {
- return value === undefined ?
- jQuery.text( this ) :
- this.empty().each( function() {
- if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
- this.textContent = value;
- }
- } );
- }, null, value, arguments.length );
- },
-
- append: function() {
- return domManip( this, arguments, function( elem ) {
- if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
- var target = manipulationTarget( this, elem );
- target.appendChild( elem );
- }
- } );
- },
-
- prepend: function() {
- return domManip( this, arguments, function( elem ) {
- if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
- var target = manipulationTarget( this, elem );
- target.insertBefore( elem, target.firstChild );
- }
- } );
- },
-
- before: function() {
- return domManip( this, arguments, function( elem ) {
- if ( this.parentNode ) {
- this.parentNode.insertBefore( elem, this );
- }
- } );
- },
-
- after: function() {
- return domManip( this, arguments, function( elem ) {
- if ( this.parentNode ) {
- this.parentNode.insertBefore( elem, this.nextSibling );
- }
- } );
- },
-
- empty: function() {
- var elem,
- i = 0;
-
- for ( ; ( elem = this[ i ] ) != null; i++ ) {
- if ( elem.nodeType === 1 ) {
-
- // Prevent memory leaks
- jQuery.cleanData( getAll( elem, false ) );
-
- // Remove any remaining nodes
- elem.textContent = "";
- }
- }
-
- return this;
- },
-
- clone: function( dataAndEvents, deepDataAndEvents ) {
- dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
- deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
-
- return this.map( function() {
- return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
- } );
- },
-
- html: function( value ) {
- return access( this, function( value ) {
- var elem = this[ 0 ] || {},
- i = 0,
- l = this.length;
-
- if ( value === undefined && elem.nodeType === 1 ) {
- return elem.innerHTML;
- }
-
- // See if we can take a shortcut and just use innerHTML
- if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
- !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {
-
- value = jQuery.htmlPrefilter( value );
-
- try {
- for ( ; i < l; i++ ) {
- elem = this[ i ] || {};
-
- // Remove element nodes and prevent memory leaks
- if ( elem.nodeType === 1 ) {
- jQuery.cleanData( getAll( elem, false ) );
- elem.innerHTML = value;
- }
- }
-
- elem = 0;
-
- // If using innerHTML throws an exception, use the fallback method
- } catch ( e ) {}
- }
-
- if ( elem ) {
- this.empty().append( value );
- }
- }, null, value, arguments.length );
- },
-
- replaceWith: function() {
- var ignored = [];
-
- // Make the changes, replacing each non-ignored context element with the new content
- return domManip( this, arguments, function( elem ) {
- var parent = this.parentNode;
-
- if ( jQuery.inArray( this, ignored ) < 0 ) {
- jQuery.cleanData( getAll( this ) );
- if ( parent ) {
- parent.replaceChild( elem, this );
- }
- }
-
- // Force callback invocation
- }, ignored );
- }
-} );
-
-jQuery.each( {
- appendTo: "append",
- prependTo: "prepend",
- insertBefore: "before",
- insertAfter: "after",
- replaceAll: "replaceWith"
-}, function( name, original ) {
- jQuery.fn[ name ] = function( selector ) {
- var elems,
- ret = [],
- insert = jQuery( selector ),
- last = insert.length - 1,
- i = 0;
-
- for ( ; i <= last; i++ ) {
- elems = i === last ? this : this.clone( true );
- jQuery( insert[ i ] )[ original ]( elems );
-
- // Support: Android <=4.0 only, PhantomJS 1 only
- // .get() because push.apply(_, arraylike) throws on ancient WebKit
- push.apply( ret, elems.get() );
- }
-
- return this.pushStack( ret );
- };
-} );
-var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
-
-var rcustomProp = /^--/;
-
-
-var getStyles = function( elem ) {
-
- // Support: IE <=11 only, Firefox <=30 (trac-15098, trac-14150)
- // IE throws on elements created in popups
- // FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
- var view = elem.ownerDocument.defaultView;
-
- if ( !view || !view.opener ) {
- view = window;
- }
-
- return view.getComputedStyle( elem );
- };
-
-var swap = function( elem, options, callback ) {
- var ret, name,
- old = {};
-
- // Remember the old values, and insert the new ones
- for ( name in options ) {
- old[ name ] = elem.style[ name ];
- elem.style[ name ] = options[ name ];
- }
-
- ret = callback.call( elem );
-
- // Revert the old values
- for ( name in options ) {
- elem.style[ name ] = old[ name ];
- }
-
- return ret;
-};
-
-
-var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
-
-
-
-( function() {
-
- // Executing both pixelPosition & boxSizingReliable tests require only one layout
- // so they're executed at the same time to save the second computation.
- function computeStyleTests() {
-
- // This is a singleton, we need to execute it only once
- if ( !div ) {
- return;
- }
-
- container.style.cssText = "position:absolute;left:-11111px;width:60px;" +
- "margin-top:1px;padding:0;border:0";
- div.style.cssText =
- "position:relative;display:block;box-sizing:border-box;overflow:scroll;" +
- "margin:auto;border:1px;padding:1px;" +
- "width:60%;top:1%";
- documentElement.appendChild( container ).appendChild( div );
-
- var divStyle = window.getComputedStyle( div );
- pixelPositionVal = divStyle.top !== "1%";
-
- // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44
- reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12;
-
- // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3
- // Some styles come back with percentage values, even though they shouldn't
- div.style.right = "60%";
- pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36;
-
- // Support: IE 9 - 11 only
- // Detect misreporting of content dimensions for box-sizing:border-box elements
- boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36;
-
- // Support: IE 9 only
- // Detect overflow:scroll screwiness (gh-3699)
- // Support: Chrome <=64
- // Don't get tricked when zoom affects offsetWidth (gh-4029)
- div.style.position = "absolute";
- scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12;
-
- documentElement.removeChild( container );
-
- // Nullify the div so it wouldn't be stored in the memory and
- // it will also be a sign that checks already performed
- div = null;
- }
-
- function roundPixelMeasures( measure ) {
- return Math.round( parseFloat( measure ) );
- }
-
- var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal,
- reliableTrDimensionsVal, reliableMarginLeftVal,
- container = document.createElement( "div" ),
- div = document.createElement( "div" );
-
- // Finish early in limited (non-browser) environments
- if ( !div.style ) {
- return;
- }
-
- // Support: IE <=9 - 11 only
- // Style of cloned element affects source element cloned (trac-8908)
- div.style.backgroundClip = "content-box";
- div.cloneNode( true ).style.backgroundClip = "";
- support.clearCloneStyle = div.style.backgroundClip === "content-box";
-
- jQuery.extend( support, {
- boxSizingReliable: function() {
- computeStyleTests();
- return boxSizingReliableVal;
- },
- pixelBoxStyles: function() {
- computeStyleTests();
- return pixelBoxStylesVal;
- },
- pixelPosition: function() {
- computeStyleTests();
- return pixelPositionVal;
- },
- reliableMarginLeft: function() {
- computeStyleTests();
- return reliableMarginLeftVal;
- },
- scrollboxSize: function() {
- computeStyleTests();
- return scrollboxSizeVal;
- },
-
- // Support: IE 9 - 11+, Edge 15 - 18+
- // IE/Edge misreport `getComputedStyle` of table rows with width/height
- // set in CSS while `offset*` properties report correct values.
- // Behavior in IE 9 is more subtle than in newer versions & it passes
- // some versions of this test; make sure not to make it pass there!
- //
- // Support: Firefox 70+
- // Only Firefox includes border widths
- // in computed dimensions. (gh-4529)
- reliableTrDimensions: function() {
- var table, tr, trChild, trStyle;
- if ( reliableTrDimensionsVal == null ) {
- table = document.createElement( "table" );
- tr = document.createElement( "tr" );
- trChild = document.createElement( "div" );
-
- table.style.cssText = "position:absolute;left:-11111px;border-collapse:separate";
- tr.style.cssText = "border:1px solid";
-
- // Support: Chrome 86+
- // Height set through cssText does not get applied.
- // Computed height then comes back as 0.
- tr.style.height = "1px";
- trChild.style.height = "9px";
-
- // Support: Android 8 Chrome 86+
- // In our bodyBackground.html iframe,
- // display for all div elements is set to "inline",
- // which causes a problem only in Android 8 Chrome 86.
- // Ensuring the div is display: block
- // gets around this issue.
- trChild.style.display = "block";
-
- documentElement
- .appendChild( table )
- .appendChild( tr )
- .appendChild( trChild );
-
- trStyle = window.getComputedStyle( tr );
- reliableTrDimensionsVal = ( parseInt( trStyle.height, 10 ) +
- parseInt( trStyle.borderTopWidth, 10 ) +
- parseInt( trStyle.borderBottomWidth, 10 ) ) === tr.offsetHeight;
-
- documentElement.removeChild( table );
- }
- return reliableTrDimensionsVal;
- }
- } );
-} )();
-
-
-function curCSS( elem, name, computed ) {
- var width, minWidth, maxWidth, ret,
- isCustomProp = rcustomProp.test( name ),
-
- // Support: Firefox 51+
- // Retrieving style before computed somehow
- // fixes an issue with getting wrong values
- // on detached elements
- style = elem.style;
-
- computed = computed || getStyles( elem );
-
- // getPropertyValue is needed for:
- // .css('filter') (IE 9 only, trac-12537)
- // .css('--customProperty) (gh-3144)
- if ( computed ) {
-
- // Support: IE <=9 - 11+
- // IE only supports `"float"` in `getPropertyValue`; in computed styles
- // it's only available as `"cssFloat"`. We no longer modify properties
- // sent to `.css()` apart from camelCasing, so we need to check both.
- // Normally, this would create difference in behavior: if
- // `getPropertyValue` returns an empty string, the value returned
- // by `.css()` would be `undefined`. This is usually the case for
- // disconnected elements. However, in IE even disconnected elements
- // with no styles return `"none"` for `getPropertyValue( "float" )`
- ret = computed.getPropertyValue( name ) || computed[ name ];
-
- if ( isCustomProp && ret ) {
-
- // Support: Firefox 105+, Chrome <=105+
- // Spec requires trimming whitespace for custom properties (gh-4926).
- // Firefox only trims leading whitespace. Chrome just collapses
- // both leading & trailing whitespace to a single space.
- //
- // Fall back to `undefined` if empty string returned.
- // This collapses a missing definition with property defined
- // and set to an empty string but there's no standard API
- // allowing us to differentiate them without a performance penalty
- // and returning `undefined` aligns with older jQuery.
- //
- // rtrimCSS treats U+000D CARRIAGE RETURN and U+000C FORM FEED
- // as whitespace while CSS does not, but this is not a problem
- // because CSS preprocessing replaces them with U+000A LINE FEED
- // (which *is* CSS whitespace)
- // https://www.w3.org/TR/css-syntax-3/#input-preprocessing
- ret = ret.replace( rtrimCSS, "$1" ) || undefined;
- }
-
- if ( ret === "" && !isAttached( elem ) ) {
- ret = jQuery.style( elem, name );
- }
-
- // A tribute to the "awesome hack by Dean Edwards"
- // Android Browser returns percentage for some values,
- // but width seems to be reliably pixels.
- // This is against the CSSOM draft spec:
- // https://drafts.csswg.org/cssom/#resolved-values
- if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) {
-
- // Remember the original values
- width = style.width;
- minWidth = style.minWidth;
- maxWidth = style.maxWidth;
-
- // Put in the new values to get a computed value out
- style.minWidth = style.maxWidth = style.width = ret;
- ret = computed.width;
-
- // Revert the changed values
- style.width = width;
- style.minWidth = minWidth;
- style.maxWidth = maxWidth;
- }
- }
-
- return ret !== undefined ?
-
- // Support: IE <=9 - 11 only
- // IE returns zIndex value as an integer.
- ret + "" :
- ret;
-}
-
-
-function addGetHookIf( conditionFn, hookFn ) {
-
- // Define the hook, we'll check on the first run if it's really needed.
- return {
- get: function() {
- if ( conditionFn() ) {
-
- // Hook not needed (or it's not possible to use it due
- // to missing dependency), remove it.
- delete this.get;
- return;
- }
-
- // Hook needed; redefine it so that the support test is not executed again.
- return ( this.get = hookFn ).apply( this, arguments );
- }
- };
-}
-
-
-var cssPrefixes = [ "Webkit", "Moz", "ms" ],
- emptyStyle = document.createElement( "div" ).style,
- vendorProps = {};
-
-// Return a vendor-prefixed property or undefined
-function vendorPropName( name ) {
-
- // Check for vendor prefixed names
- var capName = name[ 0 ].toUpperCase() + name.slice( 1 ),
- i = cssPrefixes.length;
-
- while ( i-- ) {
- name = cssPrefixes[ i ] + capName;
- if ( name in emptyStyle ) {
- return name;
- }
- }
-}
-
-// Return a potentially-mapped jQuery.cssProps or vendor prefixed property
-function finalPropName( name ) {
- var final = jQuery.cssProps[ name ] || vendorProps[ name ];
-
- if ( final ) {
- return final;
- }
- if ( name in emptyStyle ) {
- return name;
- }
- return vendorProps[ name ] = vendorPropName( name ) || name;
-}
-
-
-var
-
- // Swappable if display is none or starts with table
- // except "table", "table-cell", or "table-caption"
- // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
- rdisplayswap = /^(none|table(?!-c[ea]).+)/,
- cssShow = { position: "absolute", visibility: "hidden", display: "block" },
- cssNormalTransform = {
- letterSpacing: "0",
- fontWeight: "400"
- };
-
-function setPositiveNumber( _elem, value, subtract ) {
-
- // Any relative (+/-) values have already been
- // normalized at this point
- var matches = rcssNum.exec( value );
- return matches ?
-
- // Guard against undefined "subtract", e.g., when used as in cssHooks
- Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) :
- value;
-}
-
-function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) {
- var i = dimension === "width" ? 1 : 0,
- extra = 0,
- delta = 0,
- marginDelta = 0;
-
- // Adjustment may not be necessary
- if ( box === ( isBorderBox ? "border" : "content" ) ) {
- return 0;
- }
-
- for ( ; i < 4; i += 2 ) {
-
- // Both box models exclude margin
- // Count margin delta separately to only add it after scroll gutter adjustment.
- // This is needed to make negative margins work with `outerHeight( true )` (gh-3982).
- if ( box === "margin" ) {
- marginDelta += jQuery.css( elem, box + cssExpand[ i ], true, styles );
- }
-
- // If we get here with a content-box, we're seeking "padding" or "border" or "margin"
- if ( !isBorderBox ) {
-
- // Add padding
- delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
-
- // For "border" or "margin", add border
- if ( box !== "padding" ) {
- delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
-
- // But still keep track of it otherwise
- } else {
- extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
- }
-
- // If we get here with a border-box (content + padding + border), we're seeking "content" or
- // "padding" or "margin"
- } else {
-
- // For "content", subtract padding
- if ( box === "content" ) {
- delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
- }
-
- // For "content" or "padding", subtract border
- if ( box !== "margin" ) {
- delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
- }
- }
- }
-
- // Account for positive content-box scroll gutter when requested by providing computedVal
- if ( !isBorderBox && computedVal >= 0 ) {
-
- // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border
- // Assuming integer scroll gutter, subtract the rest and round down
- delta += Math.max( 0, Math.ceil(
- elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
- computedVal -
- delta -
- extra -
- 0.5
-
- // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter
- // Use an explicit zero to avoid NaN (gh-3964)
- ) ) || 0;
- }
-
- return delta + marginDelta;
-}
-
-function getWidthOrHeight( elem, dimension, extra ) {
-
- // Start with computed style
- var styles = getStyles( elem ),
-
- // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322).
- // Fake content-box until we know it's needed to know the true value.
- boxSizingNeeded = !support.boxSizingReliable() || extra,
- isBorderBox = boxSizingNeeded &&
- jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
- valueIsBorderBox = isBorderBox,
-
- val = curCSS( elem, dimension, styles ),
- offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 );
-
- // Support: Firefox <=54
- // Return a confounding non-pixel value or feign ignorance, as appropriate.
- if ( rnumnonpx.test( val ) ) {
- if ( !extra ) {
- return val;
- }
- val = "auto";
- }
-
-
- // Support: IE 9 - 11 only
- // Use offsetWidth/offsetHeight for when box sizing is unreliable.
- // In those cases, the computed value can be trusted to be border-box.
- if ( ( !support.boxSizingReliable() && isBorderBox ||
-
- // Support: IE 10 - 11+, Edge 15 - 18+
- // IE/Edge misreport `getComputedStyle` of table rows with width/height
- // set in CSS while `offset*` properties report correct values.
- // Interestingly, in some cases IE 9 doesn't suffer from this issue.
- !support.reliableTrDimensions() && nodeName( elem, "tr" ) ||
-
- // Fall back to offsetWidth/offsetHeight when value is "auto"
- // This happens for inline elements with no explicit setting (gh-3571)
- val === "auto" ||
-
- // Support: Android <=4.1 - 4.3 only
- // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602)
- !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) &&
-
- // Make sure the element is visible & connected
- elem.getClientRects().length ) {
-
- isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
-
- // Where available, offsetWidth/offsetHeight approximate border box dimensions.
- // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the
- // retrieved value as a content box dimension.
- valueIsBorderBox = offsetProp in elem;
- if ( valueIsBorderBox ) {
- val = elem[ offsetProp ];
- }
- }
-
- // Normalize "" and auto
- val = parseFloat( val ) || 0;
-
- // Adjust for the element's box model
- return ( val +
- boxModelAdjustment(
- elem,
- dimension,
- extra || ( isBorderBox ? "border" : "content" ),
- valueIsBorderBox,
- styles,
-
- // Provide the current computed size to request scroll gutter calculation (gh-3589)
- val
- )
- ) + "px";
-}
-
-jQuery.extend( {
-
- // Add in style property hooks for overriding the default
- // behavior of getting and setting a style property
- cssHooks: {
- opacity: {
- get: function( elem, computed ) {
- if ( computed ) {
-
- // We should always get a number back from opacity
- var ret = curCSS( elem, "opacity" );
- return ret === "" ? "1" : ret;
- }
- }
- }
- },
-
- // Don't automatically add "px" to these possibly-unitless properties
- cssNumber: {
- animationIterationCount: true,
- aspectRatio: true,
- borderImageSlice: true,
- columnCount: true,
- flexGrow: true,
- flexShrink: true,
- fontWeight: true,
- gridArea: true,
- gridColumn: true,
- gridColumnEnd: true,
- gridColumnStart: true,
- gridRow: true,
- gridRowEnd: true,
- gridRowStart: true,
- lineHeight: true,
- opacity: true,
- order: true,
- orphans: true,
- scale: true,
- widows: true,
- zIndex: true,
- zoom: true,
-
- // SVG-related
- fillOpacity: true,
- floodOpacity: true,
- stopOpacity: true,
- strokeMiterlimit: true,
- strokeOpacity: true
- },
-
- // Add in properties whose names you wish to fix before
- // setting or getting the value
- cssProps: {},
-
- // Get and set the style property on a DOM Node
- style: function( elem, name, value, extra ) {
-
- // Don't set styles on text and comment nodes
- if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
- return;
- }
-
- // Make sure that we're working with the right name
- var ret, type, hooks,
- origName = camelCase( name ),
- isCustomProp = rcustomProp.test( name ),
- style = elem.style;
-
- // Make sure that we're working with the right name. We don't
- // want to query the value if it is a CSS custom property
- // since they are user-defined.
- if ( !isCustomProp ) {
- name = finalPropName( origName );
- }
-
- // Gets hook for the prefixed version, then unprefixed version
- hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
-
- // Check if we're setting a value
- if ( value !== undefined ) {
- type = typeof value;
-
- // Convert "+=" or "-=" to relative numbers (trac-7345)
- if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
- value = adjustCSS( elem, name, ret );
-
- // Fixes bug trac-9237
- type = "number";
- }
-
- // Make sure that null and NaN values aren't set (trac-7116)
- if ( value == null || value !== value ) {
- return;
- }
-
- // If a number was passed in, add the unit (except for certain CSS properties)
- // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append
- // "px" to a few hardcoded values.
- if ( type === "number" && !isCustomProp ) {
- value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" );
- }
-
- // background-* props affect original clone's values
- if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
- style[ name ] = "inherit";
- }
-
- // If a hook was provided, use that value, otherwise just set the specified value
- if ( !hooks || !( "set" in hooks ) ||
- ( value = hooks.set( elem, value, extra ) ) !== undefined ) {
-
- if ( isCustomProp ) {
- style.setProperty( name, value );
- } else {
- style[ name ] = value;
- }
- }
-
- } else {
-
- // If a hook was provided get the non-computed value from there
- if ( hooks && "get" in hooks &&
- ( ret = hooks.get( elem, false, extra ) ) !== undefined ) {
-
- return ret;
- }
-
- // Otherwise just get the value from the style object
- return style[ name ];
- }
- },
-
- css: function( elem, name, extra, styles ) {
- var val, num, hooks,
- origName = camelCase( name ),
- isCustomProp = rcustomProp.test( name );
-
- // Make sure that we're working with the right name. We don't
- // want to modify the value if it is a CSS custom property
- // since they are user-defined.
- if ( !isCustomProp ) {
- name = finalPropName( origName );
- }
-
- // Try prefixed name followed by the unprefixed name
- hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
-
- // If a hook was provided get the computed value from there
- if ( hooks && "get" in hooks ) {
- val = hooks.get( elem, true, extra );
- }
-
- // Otherwise, if a way to get the computed value exists, use that
- if ( val === undefined ) {
- val = curCSS( elem, name, styles );
- }
-
- // Convert "normal" to computed value
- if ( val === "normal" && name in cssNormalTransform ) {
- val = cssNormalTransform[ name ];
- }
-
- // Make numeric if forced or a qualifier was provided and val looks numeric
- if ( extra === "" || extra ) {
- num = parseFloat( val );
- return extra === true || isFinite( num ) ? num || 0 : val;
- }
-
- return val;
- }
-} );
-
-jQuery.each( [ "height", "width" ], function( _i, dimension ) {
- jQuery.cssHooks[ dimension ] = {
- get: function( elem, computed, extra ) {
- if ( computed ) {
-
- // Certain elements can have dimension info if we invisibly show them
- // but it must have a current display style that would benefit
- return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&
-
- // Support: Safari 8+
- // Table columns in Safari have non-zero offsetWidth & zero
- // getBoundingClientRect().width unless display is changed.
- // Support: IE <=11 only
- // Running getBoundingClientRect on a disconnected node
- // in IE throws an error.
- ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
- swap( elem, cssShow, function() {
- return getWidthOrHeight( elem, dimension, extra );
- } ) :
- getWidthOrHeight( elem, dimension, extra );
- }
- },
-
- set: function( elem, value, extra ) {
- var matches,
- styles = getStyles( elem ),
-
- // Only read styles.position if the test has a chance to fail
- // to avoid forcing a reflow.
- scrollboxSizeBuggy = !support.scrollboxSize() &&
- styles.position === "absolute",
-
- // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991)
- boxSizingNeeded = scrollboxSizeBuggy || extra,
- isBorderBox = boxSizingNeeded &&
- jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
- subtract = extra ?
- boxModelAdjustment(
- elem,
- dimension,
- extra,
- isBorderBox,
- styles
- ) :
- 0;
-
- // Account for unreliable border-box dimensions by comparing offset* to computed and
- // faking a content-box to get border and padding (gh-3699)
- if ( isBorderBox && scrollboxSizeBuggy ) {
- subtract -= Math.ceil(
- elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
- parseFloat( styles[ dimension ] ) -
- boxModelAdjustment( elem, dimension, "border", false, styles ) -
- 0.5
- );
- }
-
- // Convert to pixels if value adjustment is needed
- if ( subtract && ( matches = rcssNum.exec( value ) ) &&
- ( matches[ 3 ] || "px" ) !== "px" ) {
-
- elem.style[ dimension ] = value;
- value = jQuery.css( elem, dimension );
- }
-
- return setPositiveNumber( elem, value, subtract );
- }
- };
-} );
-
-jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,
- function( elem, computed ) {
- if ( computed ) {
- return ( parseFloat( curCSS( elem, "marginLeft" ) ) ||
- elem.getBoundingClientRect().left -
- swap( elem, { marginLeft: 0 }, function() {
- return elem.getBoundingClientRect().left;
- } )
- ) + "px";
- }
- }
-);
-
-// These hooks are used by animate to expand properties
-jQuery.each( {
- margin: "",
- padding: "",
- border: "Width"
-}, function( prefix, suffix ) {
- jQuery.cssHooks[ prefix + suffix ] = {
- expand: function( value ) {
- var i = 0,
- expanded = {},
-
- // Assumes a single number if not a string
- parts = typeof value === "string" ? value.split( " " ) : [ value ];
-
- for ( ; i < 4; i++ ) {
- expanded[ prefix + cssExpand[ i ] + suffix ] =
- parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
- }
-
- return expanded;
- }
- };
-
- if ( prefix !== "margin" ) {
- jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
- }
-} );
-
-jQuery.fn.extend( {
- css: function( name, value ) {
- return access( this, function( elem, name, value ) {
- var styles, len,
- map = {},
- i = 0;
-
- if ( Array.isArray( name ) ) {
- styles = getStyles( elem );
- len = name.length;
-
- for ( ; i < len; i++ ) {
- map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
- }
-
- return map;
- }
-
- return value !== undefined ?
- jQuery.style( elem, name, value ) :
- jQuery.css( elem, name );
- }, name, value, arguments.length > 1 );
- }
-} );
-
-
-// Based off of the plugin by Clint Helfers, with permission.
-jQuery.fn.delay = function( time, type ) {
- time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
- type = type || "fx";
-
- return this.queue( type, function( next, hooks ) {
- var timeout = window.setTimeout( next, time );
- hooks.stop = function() {
- window.clearTimeout( timeout );
- };
- } );
-};
-
-
-( function() {
- var input = document.createElement( "input" ),
- select = document.createElement( "select" ),
- opt = select.appendChild( document.createElement( "option" ) );
-
- input.type = "checkbox";
-
- // Support: Android <=4.3 only
- // Default value for a checkbox should be "on"
- support.checkOn = input.value !== "";
-
- // Support: IE <=11 only
- // Must access selectedIndex to make default options select
- support.optSelected = opt.selected;
-
- // Support: IE <=11 only
- // An input loses its value after becoming a radio
- input = document.createElement( "input" );
- input.value = "t";
- input.type = "radio";
- support.radioValue = input.value === "t";
-} )();
-
-
-var boolHook,
- attrHandle = jQuery.expr.attrHandle;
-
-jQuery.fn.extend( {
- attr: function( name, value ) {
- return access( this, jQuery.attr, name, value, arguments.length > 1 );
- },
-
- removeAttr: function( name ) {
- return this.each( function() {
- jQuery.removeAttr( this, name );
- } );
- }
-} );
-
-jQuery.extend( {
- attr: function( elem, name, value ) {
- var ret, hooks,
- nType = elem.nodeType;
-
- // Don't get/set attributes on text, comment and attribute nodes
- if ( nType === 3 || nType === 8 || nType === 2 ) {
- return;
- }
-
- // Fallback to prop when attributes are not supported
- if ( typeof elem.getAttribute === "undefined" ) {
- return jQuery.prop( elem, name, value );
- }
-
- // Attribute hooks are determined by the lowercase version
- // Grab necessary hook if one is defined
- if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
- hooks = jQuery.attrHooks[ name.toLowerCase() ] ||
- ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined );
- }
-
- if ( value !== undefined ) {
- if ( value === null ) {
- jQuery.removeAttr( elem, name );
- return;
- }
-
- if ( hooks && "set" in hooks &&
- ( ret = hooks.set( elem, value, name ) ) !== undefined ) {
- return ret;
- }
-
- elem.setAttribute( name, value + "" );
- return value;
- }
-
- if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
- return ret;
- }
-
- ret = jQuery.find.attr( elem, name );
-
- // Non-existent attributes return null, we normalize to undefined
- return ret == null ? undefined : ret;
- },
-
- attrHooks: {
- type: {
- set: function( elem, value ) {
- if ( !support.radioValue && value === "radio" &&
- nodeName( elem, "input" ) ) {
- var val = elem.value;
- elem.setAttribute( "type", value );
- if ( val ) {
- elem.value = val;
- }
- return value;
- }
- }
- }
- },
-
- removeAttr: function( elem, value ) {
- var name,
- i = 0,
-
- // Attribute names can contain non-HTML whitespace characters
- // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
- attrNames = value && value.match( rnothtmlwhite );
-
- if ( attrNames && elem.nodeType === 1 ) {
- while ( ( name = attrNames[ i++ ] ) ) {
- elem.removeAttribute( name );
- }
- }
- }
-} );
-
-// Hooks for boolean attributes
-boolHook = {
- set: function( elem, value, name ) {
- if ( value === false ) {
-
- // Remove boolean attributes when set to false
- jQuery.removeAttr( elem, name );
- } else {
- elem.setAttribute( name, name );
- }
- return name;
- }
-};
-
-jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) {
- var getter = attrHandle[ name ] || jQuery.find.attr;
-
- attrHandle[ name ] = function( elem, name, isXML ) {
- var ret, handle,
- lowercaseName = name.toLowerCase();
-
- if ( !isXML ) {
-
- // Avoid an infinite loop by temporarily removing this function from the getter
- handle = attrHandle[ lowercaseName ];
- attrHandle[ lowercaseName ] = ret;
- ret = getter( elem, name, isXML ) != null ?
- lowercaseName :
- null;
- attrHandle[ lowercaseName ] = handle;
- }
- return ret;
- };
-} );
-
-
-
-
-var rfocusable = /^(?:input|select|textarea|button)$/i,
- rclickable = /^(?:a|area)$/i;
-
-jQuery.fn.extend( {
- prop: function( name, value ) {
- return access( this, jQuery.prop, name, value, arguments.length > 1 );
- },
-
- removeProp: function( name ) {
- return this.each( function() {
- delete this[ jQuery.propFix[ name ] || name ];
- } );
- }
-} );
-
-jQuery.extend( {
- prop: function( elem, name, value ) {
- var ret, hooks,
- nType = elem.nodeType;
-
- // Don't get/set properties on text, comment and attribute nodes
- if ( nType === 3 || nType === 8 || nType === 2 ) {
- return;
- }
-
- if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
-
- // Fix name and attach hooks
- name = jQuery.propFix[ name ] || name;
- hooks = jQuery.propHooks[ name ];
- }
-
- if ( value !== undefined ) {
- if ( hooks && "set" in hooks &&
- ( ret = hooks.set( elem, value, name ) ) !== undefined ) {
- return ret;
- }
-
- return ( elem[ name ] = value );
- }
-
- if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
- return ret;
- }
-
- return elem[ name ];
- },
-
- propHooks: {
- tabIndex: {
- get: function( elem ) {
-
- // Support: IE <=9 - 11 only
- // elem.tabIndex doesn't always return the
- // correct value when it hasn't been explicitly set
- // Use proper attribute retrieval (trac-12072)
- var tabindex = jQuery.find.attr( elem, "tabindex" );
-
- if ( tabindex ) {
- return parseInt( tabindex, 10 );
- }
-
- if (
- rfocusable.test( elem.nodeName ) ||
- rclickable.test( elem.nodeName ) &&
- elem.href
- ) {
- return 0;
- }
-
- return -1;
- }
- }
- },
-
- propFix: {
- "for": "htmlFor",
- "class": "className"
- }
-} );
-
-// Support: IE <=11 only
-// Accessing the selectedIndex property
-// forces the browser to respect setting selected
-// on the option
-// The getter ensures a default option is selected
-// when in an optgroup
-// eslint rule "no-unused-expressions" is disabled for this code
-// since it considers such accessions noop
-if ( !support.optSelected ) {
- jQuery.propHooks.selected = {
- get: function( elem ) {
-
- /* eslint no-unused-expressions: "off" */
-
- var parent = elem.parentNode;
- if ( parent && parent.parentNode ) {
- parent.parentNode.selectedIndex;
- }
- return null;
- },
- set: function( elem ) {
-
- /* eslint no-unused-expressions: "off" */
-
- var parent = elem.parentNode;
- if ( parent ) {
- parent.selectedIndex;
-
- if ( parent.parentNode ) {
- parent.parentNode.selectedIndex;
- }
- }
- }
- };
-}
-
-jQuery.each( [
- "tabIndex",
- "readOnly",
- "maxLength",
- "cellSpacing",
- "cellPadding",
- "rowSpan",
- "colSpan",
- "useMap",
- "frameBorder",
- "contentEditable"
-], function() {
- jQuery.propFix[ this.toLowerCase() ] = this;
-} );
-
-
-
-
- // Strip and collapse whitespace according to HTML spec
- // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace
- function stripAndCollapse( value ) {
- var tokens = value.match( rnothtmlwhite ) || [];
- return tokens.join( " " );
- }
-
-
-function getClass( elem ) {
- return elem.getAttribute && elem.getAttribute( "class" ) || "";
-}
-
-function classesToArray( value ) {
- if ( Array.isArray( value ) ) {
- return value;
- }
- if ( typeof value === "string" ) {
- return value.match( rnothtmlwhite ) || [];
- }
- return [];
-}
-
-jQuery.fn.extend( {
- addClass: function( value ) {
- var classNames, cur, curValue, className, i, finalValue;
-
- if ( isFunction( value ) ) {
- return this.each( function( j ) {
- jQuery( this ).addClass( value.call( this, j, getClass( this ) ) );
- } );
- }
-
- classNames = classesToArray( value );
-
- if ( classNames.length ) {
- return this.each( function() {
- curValue = getClass( this );
- cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
-
- if ( cur ) {
- for ( i = 0; i < classNames.length; i++ ) {
- className = classNames[ i ];
- if ( cur.indexOf( " " + className + " " ) < 0 ) {
- cur += className + " ";
- }
- }
-
- // Only assign if different to avoid unneeded rendering.
- finalValue = stripAndCollapse( cur );
- if ( curValue !== finalValue ) {
- this.setAttribute( "class", finalValue );
- }
- }
- } );
- }
-
- return this;
- },
-
- removeClass: function( value ) {
- var classNames, cur, curValue, className, i, finalValue;
-
- if ( isFunction( value ) ) {
- return this.each( function( j ) {
- jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) );
- } );
- }
-
- if ( !arguments.length ) {
- return this.attr( "class", "" );
- }
-
- classNames = classesToArray( value );
-
- if ( classNames.length ) {
- return this.each( function() {
- curValue = getClass( this );
-
- // This expression is here for better compressibility (see addClass)
- cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
-
- if ( cur ) {
- for ( i = 0; i < classNames.length; i++ ) {
- className = classNames[ i ];
-
- // Remove *all* instances
- while ( cur.indexOf( " " + className + " " ) > -1 ) {
- cur = cur.replace( " " + className + " ", " " );
- }
- }
-
- // Only assign if different to avoid unneeded rendering.
- finalValue = stripAndCollapse( cur );
- if ( curValue !== finalValue ) {
- this.setAttribute( "class", finalValue );
- }
- }
- } );
- }
-
- return this;
- },
-
- toggleClass: function( value, stateVal ) {
- var classNames, className, i, self,
- type = typeof value,
- isValidValue = type === "string" || Array.isArray( value );
-
- if ( isFunction( value ) ) {
- return this.each( function( i ) {
- jQuery( this ).toggleClass(
- value.call( this, i, getClass( this ), stateVal ),
- stateVal
- );
- } );
- }
-
- if ( typeof stateVal === "boolean" && isValidValue ) {
- return stateVal ? this.addClass( value ) : this.removeClass( value );
- }
-
- classNames = classesToArray( value );
-
- return this.each( function() {
- if ( isValidValue ) {
-
- // Toggle individual class names
- self = jQuery( this );
-
- for ( i = 0; i < classNames.length; i++ ) {
- className = classNames[ i ];
-
- // Check each className given, space separated list
- if ( self.hasClass( className ) ) {
- self.removeClass( className );
- } else {
- self.addClass( className );
- }
- }
-
- // Toggle whole class name
- } else if ( value === undefined || type === "boolean" ) {
- className = getClass( this );
- if ( className ) {
-
- // Store className if set
- dataPriv.set( this, "__className__", className );
- }
-
- // If the element has a class name or if we're passed `false`,
- // then remove the whole classname (if there was one, the above saved it).
- // Otherwise bring back whatever was previously saved (if anything),
- // falling back to the empty string if nothing was stored.
- if ( this.setAttribute ) {
- this.setAttribute( "class",
- className || value === false ?
- "" :
- dataPriv.get( this, "__className__" ) || ""
- );
- }
- }
- } );
- },
-
- hasClass: function( selector ) {
- var className, elem,
- i = 0;
-
- className = " " + selector + " ";
- while ( ( elem = this[ i++ ] ) ) {
- if ( elem.nodeType === 1 &&
- ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) {
- return true;
- }
- }
-
- return false;
- }
-} );
-
-
-
-
-var rreturn = /\r/g;
-
-jQuery.fn.extend( {
- val: function( value ) {
- var hooks, ret, valueIsFunction,
- elem = this[ 0 ];
-
- if ( !arguments.length ) {
- if ( elem ) {
- hooks = jQuery.valHooks[ elem.type ] ||
- jQuery.valHooks[ elem.nodeName.toLowerCase() ];
-
- if ( hooks &&
- "get" in hooks &&
- ( ret = hooks.get( elem, "value" ) ) !== undefined
- ) {
- return ret;
- }
-
- ret = elem.value;
-
- // Handle most common string cases
- if ( typeof ret === "string" ) {
- return ret.replace( rreturn, "" );
- }
-
- // Handle cases where value is null/undef or number
- return ret == null ? "" : ret;
- }
-
- return;
- }
-
- valueIsFunction = isFunction( value );
-
- return this.each( function( i ) {
- var val;
-
- if ( this.nodeType !== 1 ) {
- return;
- }
-
- if ( valueIsFunction ) {
- val = value.call( this, i, jQuery( this ).val() );
- } else {
- val = value;
- }
-
- // Treat null/undefined as ""; convert numbers to string
- if ( val == null ) {
- val = "";
-
- } else if ( typeof val === "number" ) {
- val += "";
-
- } else if ( Array.isArray( val ) ) {
- val = jQuery.map( val, function( value ) {
- return value == null ? "" : value + "";
- } );
- }
-
- hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
-
- // If set returns undefined, fall back to normal setting
- if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) {
- this.value = val;
- }
- } );
- }
-} );
-
-jQuery.extend( {
- valHooks: {
- option: {
- get: function( elem ) {
-
- var val = jQuery.find.attr( elem, "value" );
- return val != null ?
- val :
-
- // Support: IE <=10 - 11 only
- // option.text throws exceptions (trac-14686, trac-14858)
- // Strip and collapse whitespace
- // https://html.spec.whatwg.org/#strip-and-collapse-whitespace
- stripAndCollapse( jQuery.text( elem ) );
- }
- },
- select: {
- get: function( elem ) {
- var value, option, i,
- options = elem.options,
- index = elem.selectedIndex,
- one = elem.type === "select-one",
- values = one ? null : [],
- max = one ? index + 1 : options.length;
-
- if ( index < 0 ) {
- i = max;
-
- } else {
- i = one ? index : 0;
- }
-
- // Loop through all the selected options
- for ( ; i < max; i++ ) {
- option = options[ i ];
-
- // Support: IE <=9 only
- // IE8-9 doesn't update selected after form reset (trac-2551)
- if ( ( option.selected || i === index ) &&
-
- // Don't return options that are disabled or in a disabled optgroup
- !option.disabled &&
- ( !option.parentNode.disabled ||
- !nodeName( option.parentNode, "optgroup" ) ) ) {
-
- // Get the specific value for the option
- value = jQuery( option ).val();
-
- // We don't need an array for one selects
- if ( one ) {
- return value;
- }
-
- // Multi-Selects return an array
- values.push( value );
- }
- }
-
- return values;
- },
-
- set: function( elem, value ) {
- var optionSet, option,
- options = elem.options,
- values = jQuery.makeArray( value ),
- i = options.length;
-
- while ( i-- ) {
- option = options[ i ];
-
- /* eslint-disable no-cond-assign */
-
- if ( option.selected =
- jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1
- ) {
- optionSet = true;
- }
-
- /* eslint-enable no-cond-assign */
- }
-
- // Force browsers to behave consistently when non-matching value is set
- if ( !optionSet ) {
- elem.selectedIndex = -1;
- }
- return values;
- }
- }
- }
-} );
-
-// Radios and checkboxes getter/setter
-jQuery.each( [ "radio", "checkbox" ], function() {
- jQuery.valHooks[ this ] = {
- set: function( elem, value ) {
- if ( Array.isArray( value ) ) {
- return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 );
- }
- }
- };
- if ( !support.checkOn ) {
- jQuery.valHooks[ this ].get = function( elem ) {
- return elem.getAttribute( "value" ) === null ? "on" : elem.value;
- };
- }
-} );
-
-
-
-
-// Return jQuery for attributes-only inclusion
-
-
-// Cross-browser xml parsing
-jQuery.parseXML = function( data ) {
- var xml, parserErrorElem;
- if ( !data || typeof data !== "string" ) {
- return null;
- }
-
- // Support: IE 9 - 11 only
- // IE throws on parseFromString with invalid input.
- try {
- xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" );
- } catch ( e ) {}
-
- parserErrorElem = xml && xml.getElementsByTagName( "parsererror" )[ 0 ];
- if ( !xml || parserErrorElem ) {
- jQuery.error( "Invalid XML: " + (
- parserErrorElem ?
- jQuery.map( parserErrorElem.childNodes, function( el ) {
- return el.textContent;
- } ).join( "\n" ) :
- data
- ) );
- }
- return xml;
-};
-
-
-var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
- stopPropagationCallback = function( e ) {
- e.stopPropagation();
- };
-
-jQuery.extend( jQuery.event, {
-
- trigger: function( event, data, elem, onlyHandlers ) {
-
- var i, cur, tmp, bubbleType, ontype, handle, special, lastElement,
- eventPath = [ elem || document ],
- type = hasOwn.call( event, "type" ) ? event.type : event,
- namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : [];
-
- cur = lastElement = tmp = elem = elem || document;
-
- // Don't do events on text and comment nodes
- if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
- return;
- }
-
- // focus/blur morphs to focusin/out; ensure we're not firing them right now
- if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
- return;
- }
-
- if ( type.indexOf( "." ) > -1 ) {
-
- // Namespaced trigger; create a regexp to match event type in handle()
- namespaces = type.split( "." );
- type = namespaces.shift();
- namespaces.sort();
- }
- ontype = type.indexOf( ":" ) < 0 && "on" + type;
-
- // Caller can pass in a jQuery.Event object, Object, or just an event type string
- event = event[ jQuery.expando ] ?
- event :
- new jQuery.Event( type, typeof event === "object" && event );
-
- // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
- event.isTrigger = onlyHandlers ? 2 : 3;
- event.namespace = namespaces.join( "." );
- event.rnamespace = event.namespace ?
- new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) :
- null;
-
- // Clean up the event in case it is being reused
- event.result = undefined;
- if ( !event.target ) {
- event.target = elem;
- }
-
- // Clone any incoming data and prepend the event, creating the handler arg list
- data = data == null ?
- [ event ] :
- jQuery.makeArray( data, [ event ] );
-
- // Allow special events to draw outside the lines
- special = jQuery.event.special[ type ] || {};
- if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
- return;
- }
-
- // Determine event propagation path in advance, per W3C events spec (trac-9951)
- // Bubble up to document, then to window; watch for a global ownerDocument var (trac-9724)
- if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) {
-
- bubbleType = special.delegateType || type;
- if ( !rfocusMorph.test( bubbleType + type ) ) {
- cur = cur.parentNode;
- }
- for ( ; cur; cur = cur.parentNode ) {
- eventPath.push( cur );
- tmp = cur;
- }
-
- // Only add window if we got to document (e.g., not plain obj or detached DOM)
- if ( tmp === ( elem.ownerDocument || document ) ) {
- eventPath.push( tmp.defaultView || tmp.parentWindow || window );
- }
- }
-
- // Fire handlers on the event path
- i = 0;
- while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {
- lastElement = cur;
- event.type = i > 1 ?
- bubbleType :
- special.bindType || type;
-
- // jQuery handler
- handle = ( dataPriv.get( cur, "events" ) || Object.create( null ) )[ event.type ] &&
- dataPriv.get( cur, "handle" );
- if ( handle ) {
- handle.apply( cur, data );
- }
-
- // Native handler
- handle = ontype && cur[ ontype ];
- if ( handle && handle.apply && acceptData( cur ) ) {
- event.result = handle.apply( cur, data );
- if ( event.result === false ) {
- event.preventDefault();
- }
- }
- }
- event.type = type;
-
- // If nobody prevented the default action, do it now
- if ( !onlyHandlers && !event.isDefaultPrevented() ) {
-
- if ( ( !special._default ||
- special._default.apply( eventPath.pop(), data ) === false ) &&
- acceptData( elem ) ) {
-
- // Call a native DOM method on the target with the same name as the event.
- // Don't do default actions on window, that's where global variables be (trac-6170)
- if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) {
-
- // Don't re-trigger an onFOO event when we call its FOO() method
- tmp = elem[ ontype ];
-
- if ( tmp ) {
- elem[ ontype ] = null;
- }
-
- // Prevent re-triggering of the same event, since we already bubbled it above
- jQuery.event.triggered = type;
-
- if ( event.isPropagationStopped() ) {
- lastElement.addEventListener( type, stopPropagationCallback );
- }
-
- elem[ type ]();
-
- if ( event.isPropagationStopped() ) {
- lastElement.removeEventListener( type, stopPropagationCallback );
- }
-
- jQuery.event.triggered = undefined;
-
- if ( tmp ) {
- elem[ ontype ] = tmp;
- }
- }
- }
- }
-
- return event.result;
- },
-
- // Piggyback on a donor event to simulate a different one
- // Used only for `focus(in | out)` events
- simulate: function( type, elem, event ) {
- var e = jQuery.extend(
- new jQuery.Event(),
- event,
- {
- type: type,
- isSimulated: true
- }
- );
-
- jQuery.event.trigger( e, null, elem );
- }
-
-} );
-
-jQuery.fn.extend( {
-
- trigger: function( type, data ) {
- return this.each( function() {
- jQuery.event.trigger( type, data, this );
- } );
- },
- triggerHandler: function( type, data ) {
- var elem = this[ 0 ];
- if ( elem ) {
- return jQuery.event.trigger( type, data, elem, true );
- }
- }
-} );
-
-
-var
- rbracket = /\[\]$/,
- rCRLF = /\r?\n/g,
- rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
- rsubmittable = /^(?:input|select|textarea|keygen)/i;
-
-function buildParams( prefix, obj, traditional, add ) {
- var name;
-
- if ( Array.isArray( obj ) ) {
-
- // Serialize array item.
- jQuery.each( obj, function( i, v ) {
- if ( traditional || rbracket.test( prefix ) ) {
-
- // Treat each array item as a scalar.
- add( prefix, v );
-
- } else {
-
- // Item is non-scalar (array or object), encode its numeric index.
- buildParams(
- prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]",
- v,
- traditional,
- add
- );
- }
- } );
-
- } else if ( !traditional && toType( obj ) === "object" ) {
-
- // Serialize object item.
- for ( name in obj ) {
- buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
- }
-
- } else {
-
- // Serialize scalar item.
- add( prefix, obj );
- }
-}
-
-// Serialize an array of form elements or a set of
-// key/values into a query string
-jQuery.param = function( a, traditional ) {
- var prefix,
- s = [],
- add = function( key, valueOrFunction ) {
-
- // If value is a function, invoke it and use its return value
- var value = isFunction( valueOrFunction ) ?
- valueOrFunction() :
- valueOrFunction;
-
- s[ s.length ] = encodeURIComponent( key ) + "=" +
- encodeURIComponent( value == null ? "" : value );
- };
-
- if ( a == null ) {
- return "";
- }
-
- // If an array was passed in, assume that it is an array of form elements.
- if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
-
- // Serialize the form elements
- jQuery.each( a, function() {
- add( this.name, this.value );
- } );
-
- } else {
-
- // If traditional, encode the "old" way (the way 1.3.2 or older
- // did it), otherwise encode params recursively.
- for ( prefix in a ) {
- buildParams( prefix, a[ prefix ], traditional, add );
- }
- }
-
- // Return the resulting serialization
- return s.join( "&" );
-};
-
-jQuery.fn.extend( {
- serialize: function() {
- return jQuery.param( this.serializeArray() );
- },
- serializeArray: function() {
- return this.map( function() {
-
- // Can add propHook for "elements" to filter or add form elements
- var elements = jQuery.prop( this, "elements" );
- return elements ? jQuery.makeArray( elements ) : this;
- } ).filter( function() {
- var type = this.type;
-
- // Use .is( ":disabled" ) so that fieldset[disabled] works
- return this.name && !jQuery( this ).is( ":disabled" ) &&
- rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
- ( this.checked || !rcheckableType.test( type ) );
- } ).map( function( _i, elem ) {
- var val = jQuery( this ).val();
-
- if ( val == null ) {
- return null;
- }
-
- if ( Array.isArray( val ) ) {
- return jQuery.map( val, function( val ) {
- return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
- } );
- }
-
- return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
- } ).get();
- }
-} );
-
-
-jQuery.fn.extend( {
- wrapAll: function( html ) {
- var wrap;
-
- if ( this[ 0 ] ) {
- if ( isFunction( html ) ) {
- html = html.call( this[ 0 ] );
- }
-
- // The elements to wrap the target around
- wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );
-
- if ( this[ 0 ].parentNode ) {
- wrap.insertBefore( this[ 0 ] );
- }
-
- wrap.map( function() {
- var elem = this;
-
- while ( elem.firstElementChild ) {
- elem = elem.firstElementChild;
- }
-
- return elem;
- } ).append( this );
- }
-
- return this;
- },
-
- wrapInner: function( html ) {
- if ( isFunction( html ) ) {
- return this.each( function( i ) {
- jQuery( this ).wrapInner( html.call( this, i ) );
- } );
- }
-
- return this.each( function() {
- var self = jQuery( this ),
- contents = self.contents();
-
- if ( contents.length ) {
- contents.wrapAll( html );
-
- } else {
- self.append( html );
- }
- } );
- },
-
- wrap: function( html ) {
- var htmlIsFunction = isFunction( html );
-
- return this.each( function( i ) {
- jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html );
- } );
- },
-
- unwrap: function( selector ) {
- this.parent( selector ).not( "body" ).each( function() {
- jQuery( this ).replaceWith( this.childNodes );
- } );
- return this;
- }
-} );
-
-
-jQuery.expr.pseudos.hidden = function( elem ) {
- return !jQuery.expr.pseudos.visible( elem );
-};
-jQuery.expr.pseudos.visible = function( elem ) {
- return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
-};
-
-
-
-
-// Support: Safari 8 only
-// In Safari 8 documents created via document.implementation.createHTMLDocument
-// collapse sibling forms: the second one becomes a child of the first one.
-// Because of that, this security measure has to be disabled in Safari 8.
-// https://bugs.webkit.org/show_bug.cgi?id=137337
-support.createHTMLDocument = ( function() {
- var body = document.implementation.createHTMLDocument( "" ).body;
- body.innerHTML = "<form></form><form></form>";
- return body.childNodes.length === 2;
-} )();
-
-
-// Argument "data" should be string of html
-// context (optional): If specified, the fragment will be created in this context,
-// defaults to document
-// keepScripts (optional): If true, will include scripts passed in the html string
-jQuery.parseHTML = function( data, context, keepScripts ) {
- if ( typeof data !== "string" ) {
- return [];
- }
- if ( typeof context === "boolean" ) {
- keepScripts = context;
- context = false;
- }
-
- var base, parsed, scripts;
-
- if ( !context ) {
-
- // Stop scripts or inline event handlers from being executed immediately
- // by using document.implementation
- if ( support.createHTMLDocument ) {
- context = document.implementation.createHTMLDocument( "" );
-
- // Set the base href for the created document
- // so any parsed elements with URLs
- // are based on the document's URL (gh-2965)
- base = context.createElement( "base" );
- base.href = document.location.href;
- context.head.appendChild( base );
- } else {
- context = document;
- }
- }
-
- parsed = rsingleTag.exec( data );
- scripts = !keepScripts && [];
-
- // Single tag
- if ( parsed ) {
- return [ context.createElement( parsed[ 1 ] ) ];
- }
-
- parsed = buildFragment( [ data ], context, scripts );
-
- if ( scripts && scripts.length ) {
- jQuery( scripts ).remove();
- }
-
- return jQuery.merge( [], parsed.childNodes );
-};
-
-
-jQuery.offset = {
- setOffset: function( elem, options, i ) {
- var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
- position = jQuery.css( elem, "position" ),
- curElem = jQuery( elem ),
- props = {};
-
- // Set position first, in-case top/left are set even on static elem
- if ( position === "static" ) {
- elem.style.position = "relative";
- }
-
- curOffset = curElem.offset();
- curCSSTop = jQuery.css( elem, "top" );
- curCSSLeft = jQuery.css( elem, "left" );
- calculatePosition = ( position === "absolute" || position === "fixed" ) &&
- ( curCSSTop + curCSSLeft ).indexOf( "auto" ) > -1;
-
- // Need to be able to calculate position if either
- // top or left is auto and position is either absolute or fixed
- if ( calculatePosition ) {
- curPosition = curElem.position();
- curTop = curPosition.top;
- curLeft = curPosition.left;
-
- } else {
- curTop = parseFloat( curCSSTop ) || 0;
- curLeft = parseFloat( curCSSLeft ) || 0;
- }
-
- if ( isFunction( options ) ) {
-
- // Use jQuery.extend here to allow modification of coordinates argument (gh-1848)
- options = options.call( elem, i, jQuery.extend( {}, curOffset ) );
- }
-
- if ( options.top != null ) {
- props.top = ( options.top - curOffset.top ) + curTop;
- }
- if ( options.left != null ) {
- props.left = ( options.left - curOffset.left ) + curLeft;
- }
-
- if ( "using" in options ) {
- options.using.call( elem, props );
-
- } else {
- curElem.css( props );
- }
- }
-};
-
-jQuery.fn.extend( {
-
- // offset() relates an element's border box to the document origin
- offset: function( options ) {
-
- // Preserve chaining for setter
- if ( arguments.length ) {
- return options === undefined ?
- this :
- this.each( function( i ) {
- jQuery.offset.setOffset( this, options, i );
- } );
- }
-
- var rect, win,
- elem = this[ 0 ];
-
- if ( !elem ) {
- return;
- }
-
- // Return zeros for disconnected and hidden (display: none) elements (gh-2310)
- // Support: IE <=11 only
- // Running getBoundingClientRect on a
- // disconnected node in IE throws an error
- if ( !elem.getClientRects().length ) {
- return { top: 0, left: 0 };
- }
-
- // Get document-relative position by adding viewport scroll to viewport-relative gBCR
- rect = elem.getBoundingClientRect();
- win = elem.ownerDocument.defaultView;
- return {
- top: rect.top + win.pageYOffset,
- left: rect.left + win.pageXOffset
- };
- },
-
- // position() relates an element's margin box to its offset parent's padding box
- // This corresponds to the behavior of CSS absolute positioning
- position: function() {
- if ( !this[ 0 ] ) {
- return;
- }
-
- var offsetParent, offset, doc,
- elem = this[ 0 ],
- parentOffset = { top: 0, left: 0 };
-
- // position:fixed elements are offset from the viewport, which itself always has zero offset
- if ( jQuery.css( elem, "position" ) === "fixed" ) {
-
- // Assume position:fixed implies availability of getBoundingClientRect
- offset = elem.getBoundingClientRect();
-
- } else {
- offset = this.offset();
-
- // Account for the *real* offset parent, which can be the document or its root element
- // when a statically positioned element is identified
- doc = elem.ownerDocument;
- offsetParent = elem.offsetParent || doc.documentElement;
- while ( offsetParent &&
- ( offsetParent === doc.body || offsetParent === doc.documentElement ) &&
- jQuery.css( offsetParent, "position" ) === "static" ) {
-
- offsetParent = offsetParent.parentNode;
- }
- if ( offsetParent && offsetParent !== elem && offsetParent.nodeType === 1 ) {
-
- // Incorporate borders into its offset, since they are outside its content origin
- parentOffset = jQuery( offsetParent ).offset();
- parentOffset.top += jQuery.css( offsetParent, "borderTopWidth", true );
- parentOffset.left += jQuery.css( offsetParent, "borderLeftWidth", true );
- }
- }
-
- // Subtract parent offsets and element margins
- return {
- top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
- left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
- };
- },
-
- // This method will return documentElement in the following cases:
- // 1) For the element inside the iframe without offsetParent, this method will return
- // documentElement of the parent window
- // 2) For the hidden or detached element
- // 3) For body or html element, i.e. in case of the html node - it will return itself
- //
- // but those exceptions were never presented as a real life use-cases
- // and might be considered as more preferable results.
- //
- // This logic, however, is not guaranteed and can change at any point in the future
- offsetParent: function() {
- return this.map( function() {
- var offsetParent = this.offsetParent;
-
- while ( offsetParent && jQuery.css( offsetParent, "position" ) === "static" ) {
- offsetParent = offsetParent.offsetParent;
- }
-
- return offsetParent || documentElement;
- } );
- }
-} );
-
-// Create scrollLeft and scrollTop methods
-jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
- var top = "pageYOffset" === prop;
-
- jQuery.fn[ method ] = function( val ) {
- return access( this, function( elem, method, val ) {
-
- // Coalesce documents and windows
- var win;
- if ( isWindow( elem ) ) {
- win = elem;
- } else if ( elem.nodeType === 9 ) {
- win = elem.defaultView;
- }
-
- if ( val === undefined ) {
- return win ? win[ prop ] : elem[ method ];
- }
-
- if ( win ) {
- win.scrollTo(
- !top ? val : win.pageXOffset,
- top ? val : win.pageYOffset
- );
-
- } else {
- elem[ method ] = val;
- }
- }, method, val, arguments.length );
- };
-} );
-
-// Support: Safari <=7 - 9.1, Chrome <=37 - 49
-// Add the top/left cssHooks using jQuery.fn.position
-// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
-// Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347
-// getComputedStyle returns percent when specified for top/left/bottom/right;
-// rather than make the css module depend on the offset module, just check for it here
-jQuery.each( [ "top", "left" ], function( _i, prop ) {
- jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
- function( elem, computed ) {
- if ( computed ) {
- computed = curCSS( elem, prop );
-
- // If curCSS returns percentage, fallback to offset
- return rnumnonpx.test( computed ) ?
- jQuery( elem ).position()[ prop ] + "px" :
- computed;
- }
- }
- );
-} );
-
-
-// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
-jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
- jQuery.each( {
- padding: "inner" + name,
- content: type,
- "": "outer" + name
- }, function( defaultExtra, funcName ) {
-
- // Margin is only for outerHeight, outerWidth
- jQuery.fn[ funcName ] = function( margin, value ) {
- var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
- extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
-
- return access( this, function( elem, type, value ) {
- var doc;
-
- if ( isWindow( elem ) ) {
-
- // $( window ).outerWidth/Height return w/h including scrollbars (gh-1729)
- return funcName.indexOf( "outer" ) === 0 ?
- elem[ "inner" + name ] :
- elem.document.documentElement[ "client" + name ];
- }
-
- // Get document width or height
- if ( elem.nodeType === 9 ) {
- doc = elem.documentElement;
-
- // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
- // whichever is greatest
- return Math.max(
- elem.body[ "scroll" + name ], doc[ "scroll" + name ],
- elem.body[ "offset" + name ], doc[ "offset" + name ],
- doc[ "client" + name ]
- );
- }
-
- return value === undefined ?
-
- // Get width or height on the element, requesting but not forcing parseFloat
- jQuery.css( elem, type, extra ) :
-
- // Set width or height on the element
- jQuery.style( elem, type, value, extra );
- }, type, chainable ? margin : undefined, chainable );
- };
- } );
-} );
-
-
-jQuery.fn.extend( {
-
- bind: function( types, data, fn ) {
- return this.on( types, null, data, fn );
- },
- unbind: function( types, fn ) {
- return this.off( types, null, fn );
- },
-
- delegate: function( selector, types, data, fn ) {
- return this.on( types, selector, data, fn );
- },
- undelegate: function( selector, types, fn ) {
-
- // ( namespace ) or ( selector, types [, fn] )
- return arguments.length === 1 ?
- this.off( selector, "**" ) :
- this.off( types, selector || "**", fn );
- },
-
- hover: function( fnOver, fnOut ) {
- return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
- }
-} );
-
-jQuery.each(
- ( "blur focus focusin focusout resize scroll click dblclick " +
- "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
- "change select submit keydown keypress keyup contextmenu" ).split( " " ),
- function( _i, name ) {
-
- // Handle event binding
- jQuery.fn[ name ] = function( data, fn ) {
- return arguments.length > 0 ?
- this.on( name, null, data, fn ) :
- this.trigger( name );
- };
- }
-);
-
-
-
-
-// Support: Android <=4.0 only
-// Make sure we trim BOM and NBSP
-// Require that the "whitespace run" starts from a non-whitespace
-// to avoid O(N^2) behavior when the engine would try matching "\s+$" at each space position.
-var rtrim = /^[\s\uFEFF\xA0]+|([^\s\uFEFF\xA0])[\s\uFEFF\xA0]+$/g;
-
-// Bind a function to a context, optionally partially applying any
-// arguments.
-// jQuery.proxy is deprecated to promote standards (specifically Function#bind)
-// However, it is not slated for removal any time soon
-jQuery.proxy = function( fn, context ) {
- var tmp, args, proxy;
-
- if ( typeof context === "string" ) {
- tmp = fn[ context ];
- context = fn;
- fn = tmp;
- }
-
- // Quick check to determine if target is callable, in the spec
- // this throws a TypeError, but we will just return undefined.
- if ( !isFunction( fn ) ) {
- return undefined;
- }
-
- // Simulated bind
- args = slice.call( arguments, 2 );
- proxy = function() {
- return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
- };
-
- // Set the guid of unique handler to the same of original handler, so it can be removed
- proxy.guid = fn.guid = fn.guid || jQuery.guid++;
-
- return proxy;
-};
-
-jQuery.holdReady = function( hold ) {
- if ( hold ) {
- jQuery.readyWait++;
- } else {
- jQuery.ready( true );
- }
-};
-jQuery.isArray = Array.isArray;
-jQuery.parseJSON = JSON.parse;
-jQuery.nodeName = nodeName;
-jQuery.isFunction = isFunction;
-jQuery.isWindow = isWindow;
-jQuery.camelCase = camelCase;
-jQuery.type = toType;
-
-jQuery.now = Date.now;
-
-jQuery.isNumeric = function( obj ) {
-
- // As of jQuery 3.0, isNumeric is limited to
- // strings and numbers (primitives or objects)
- // that can be coerced to finite numbers (gh-2662)
- var type = jQuery.type( obj );
- return ( type === "number" || type === "string" ) &&
-
- // parseFloat NaNs numeric-cast false positives ("")
- // ...but misinterprets leading-number strings, particularly hex literals ("0x...")
- // subtraction forces infinities to NaN
- !isNaN( obj - parseFloat( obj ) );
-};
-
-jQuery.trim = function( text ) {
- return text == null ?
- "" :
- ( text + "" ).replace( rtrim, "$1" );
-};
-
-
-
-// Register as a named AMD module, since jQuery can be concatenated with other
-// files that may use define, but not via a proper concatenation script that
-// understands anonymous AMD modules. A named AMD is safest and most robust
-// way to register. Lowercase jquery is used because AMD module names are
-// derived from file names, and jQuery is normally delivered in a lowercase
-// file name. Do this after creating the global so that if an AMD module wants
-// to call noConflict to hide this version of jQuery, it will work.
-
-// Note that for maximum portability, libraries that are not jQuery should
-// declare themselves as anonymous modules, and avoid setting a global if an
-// AMD loader is present. jQuery is a special case. For more information, see
-// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
-
-if ( typeof define === "function" && define.amd ) {
- define( "jquery", [], function() {
- return jQuery;
- } );
-}
-
-
-
-
-var
-
- // Map over jQuery in case of overwrite
- _jQuery = window.jQuery,
-
- // Map over the $ in case of overwrite
- _$ = window.$;
-
-jQuery.noConflict = function( deep ) {
- if ( window.$ === jQuery ) {
- window.$ = _$;
- }
-
- if ( deep && window.jQuery === jQuery ) {
- window.jQuery = _jQuery;
- }
-
- return jQuery;
-};
-
-// Expose jQuery and $ identifiers, even in AMD
-// (trac-7102#comment:10, https://github.com/jquery/jquery/pull/557)
-// and CommonJS for browser emulators (trac-13566)
-if ( typeof noGlobal === "undefined" ) {
- window.jQuery = window.$ = jQuery;
-}
-
-
-
-
-return jQuery;
-} );
diff --git a/src/static/scripts/jquery-3.7.1.slim.js b/src/static/scripts/jquery-3.7.1.slim.js
@@ -0,0 +1,8617 @@
+/*!
+ * jQuery JavaScript Library v3.7.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/animatedSelector,-effects/Tween
+ * https://jquery.com/
+ *
+ * Copyright OpenJS Foundation and other contributors
+ * Released under the MIT license
+ * https://jquery.org/license
+ *
+ * Date: 2023-08-28T13:37Z
+ */
+( function( global, factory ) {
+
+ "use strict";
+
+ if ( typeof module === "object" && typeof module.exports === "object" ) {
+
+ // For CommonJS and CommonJS-like environments where a proper `window`
+ // is present, execute the factory and get jQuery.
+ // For environments that do not have a `window` with a `document`
+ // (such as Node.js), expose a factory as module.exports.
+ // This accentuates the need for the creation of a real `window`.
+ // e.g. var jQuery = require("jquery")(window);
+ // See ticket trac-14549 for more info.
+ module.exports = global.document ?
+ factory( global, true ) :
+ function( w ) {
+ if ( !w.document ) {
+ throw new Error( "jQuery requires a window with a document" );
+ }
+ return factory( w );
+ };
+ } else {
+ factory( global );
+ }
+
+// Pass this if window is not defined yet
+} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
+
+// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
+// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
+// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
+// enough that all such attempts are guarded in a try block.
+"use strict";
+
+var arr = [];
+
+var getProto = Object.getPrototypeOf;
+
+var slice = arr.slice;
+
+var flat = arr.flat ? function( array ) {
+ return arr.flat.call( array );
+} : function( array ) {
+ return arr.concat.apply( [], array );
+};
+
+
+var push = arr.push;
+
+var indexOf = arr.indexOf;
+
+var class2type = {};
+
+var toString = class2type.toString;
+
+var hasOwn = class2type.hasOwnProperty;
+
+var fnToString = hasOwn.toString;
+
+var ObjectFunctionString = fnToString.call( Object );
+
+var support = {};
+
+var isFunction = function isFunction( obj ) {
+
+ // Support: Chrome <=57, Firefox <=52
+ // In some browsers, typeof returns "function" for HTML <object> elements
+ // (i.e., `typeof document.createElement( "object" ) === "function"`).
+ // We don't want to classify *any* DOM node as a function.
+ // Support: QtWeb <=3.8.5, WebKit <=534.34, wkhtmltopdf tool <=0.12.5
+ // Plus for old WebKit, typeof returns "function" for HTML collections
+ // (e.g., `typeof document.getElementsByTagName("div") === "function"`). (gh-4756)
+ return typeof obj === "function" && typeof obj.nodeType !== "number" &&
+ typeof obj.item !== "function";
+ };
+
+
+var isWindow = function isWindow( obj ) {
+ return obj != null && obj === obj.window;
+ };
+
+
+var document = window.document;
+
+
+
+ var preservedScriptAttributes = {
+ type: true,
+ src: true,
+ nonce: true,
+ noModule: true
+ };
+
+ function DOMEval( code, node, doc ) {
+ doc = doc || document;
+
+ var i, val,
+ script = doc.createElement( "script" );
+
+ script.text = code;
+ if ( node ) {
+ for ( i in preservedScriptAttributes ) {
+
+ // Support: Firefox 64+, Edge 18+
+ // Some browsers don't support the "nonce" property on scripts.
+ // On the other hand, just using `getAttribute` is not enough as
+ // the `nonce` attribute is reset to an empty string whenever it
+ // becomes browsing-context connected.
+ // See https://github.com/whatwg/html/issues/2369
+ // See https://html.spec.whatwg.org/#nonce-attributes
+ // The `node.getAttribute` check was added for the sake of
+ // `jQuery.globalEval` so that it can fake a nonce-containing node
+ // via an object.
+ val = node[ i ] || node.getAttribute && node.getAttribute( i );
+ if ( val ) {
+ script.setAttribute( i, val );
+ }
+ }
+ }
+ doc.head.appendChild( script ).parentNode.removeChild( script );
+ }
+
+
+function toType( obj ) {
+ if ( obj == null ) {
+ return obj + "";
+ }
+
+ // Support: Android <=2.3 only (functionish RegExp)
+ return typeof obj === "object" || typeof obj === "function" ?
+ class2type[ toString.call( obj ) ] || "object" :
+ typeof obj;
+}
+/* global Symbol */
+// Defining this global in .eslintrc.json would create a danger of using the global
+// unguarded in another place, it seems safer to define global only for this module
+
+
+
+var version = "3.7.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/animatedSelector,-effects/Tween",
+
+ rhtmlSuffix = /HTML$/i,
+
+ // Define a local copy of jQuery
+ jQuery = function( selector, context ) {
+
+ // The jQuery object is actually just the init constructor 'enhanced'
+ // Need init if jQuery is called (just allow error to be thrown if not included)
+ return new jQuery.fn.init( selector, context );
+ };
+
+jQuery.fn = jQuery.prototype = {
+
+ // The current version of jQuery being used
+ jquery: version,
+
+ constructor: jQuery,
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ toArray: function() {
+ return slice.call( this );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+
+ // Return all the elements in a clean array
+ if ( num == null ) {
+ return slice.call( this );
+ }
+
+ // Return just the one element from the set
+ return num < 0 ? this[ num + this.length ] : this[ num ];
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems ) {
+
+ // Build a new jQuery matched element set
+ var ret = jQuery.merge( this.constructor(), elems );
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ each: function( callback ) {
+ return jQuery.each( this, callback );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map( this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ } ) );
+ },
+
+ slice: function() {
+ return this.pushStack( slice.apply( this, arguments ) );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ even: function() {
+ return this.pushStack( jQuery.grep( this, function( _elem, i ) {
+ return ( i + 1 ) % 2;
+ } ) );
+ },
+
+ odd: function() {
+ return this.pushStack( jQuery.grep( this, function( _elem, i ) {
+ return i % 2;
+ } ) );
+ },
+
+ eq: function( i ) {
+ var len = this.length,
+ j = +i + ( i < 0 ? len : 0 );
+ return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor();
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: push,
+ sort: arr.sort,
+ splice: arr.splice
+};
+
+jQuery.extend = jQuery.fn.extend = function() {
+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[ 0 ] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+
+ // Skip the boolean and the target
+ target = arguments[ i ] || {};
+ i++;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !isFunction( target ) ) {
+ target = {};
+ }
+
+ // Extend jQuery itself if only one argument is passed
+ if ( i === length ) {
+ target = this;
+ i--;
+ }
+
+ for ( ; i < length; i++ ) {
+
+ // Only deal with non-null/undefined values
+ if ( ( options = arguments[ i ] ) != null ) {
+
+ // Extend the base object
+ for ( name in options ) {
+ copy = options[ name ];
+
+ // Prevent Object.prototype pollution
+ // Prevent never-ending loop
+ if ( name === "__proto__" || target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
+ ( copyIsArray = Array.isArray( copy ) ) ) ) {
+ src = target[ name ];
+
+ // Ensure proper type for the source value
+ if ( copyIsArray && !Array.isArray( src ) ) {
+ clone = [];
+ } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) {
+ clone = {};
+ } else {
+ clone = src;
+ }
+ copyIsArray = false;
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend( {
+
+ // Unique for each copy of jQuery on the page
+ expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
+
+ // Assume jQuery is ready without the ready module
+ isReady: true,
+
+ error: function( msg ) {
+ throw new Error( msg );
+ },
+
+ noop: function() {},
+
+ isPlainObject: function( obj ) {
+ var proto, Ctor;
+
+ // Detect obvious negatives
+ // Use toString instead of jQuery.type to catch host objects
+ if ( !obj || toString.call( obj ) !== "[object Object]" ) {
+ return false;
+ }
+
+ proto = getProto( obj );
+
+ // Objects with no prototype (e.g., `Object.create( null )`) are plain
+ if ( !proto ) {
+ return true;
+ }
+
+ // Objects with prototype are plain iff they were constructed by a global Object function
+ Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
+ return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
+ },
+
+ isEmptyObject: function( obj ) {
+ var name;
+
+ for ( name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ // Evaluates a script in a provided context; falls back to the global one
+ // if not specified.
+ globalEval: function( code, options, doc ) {
+ DOMEval( code, { nonce: options && options.nonce }, doc );
+ },
+
+ each: function( obj, callback ) {
+ var length, i = 0;
+
+ if ( isArrayLike( obj ) ) {
+ length = obj.length;
+ for ( ; i < length; i++ ) {
+ if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( i in obj ) {
+ if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
+ break;
+ }
+ }
+ }
+
+ return obj;
+ },
+
+
+ // Retrieve the text value of an array of DOM nodes
+ text: function( elem ) {
+ var node,
+ ret = "",
+ i = 0,
+ nodeType = elem.nodeType;
+
+ if ( !nodeType ) {
+
+ // If no nodeType, this is expected to be an array
+ while ( ( node = elem[ i++ ] ) ) {
+
+ // Do not traverse comment nodes
+ ret += jQuery.text( node );
+ }
+ }
+ if ( nodeType === 1 || nodeType === 11 ) {
+ return elem.textContent;
+ }
+ if ( nodeType === 9 ) {
+ return elem.documentElement.textContent;
+ }
+ if ( nodeType === 3 || nodeType === 4 ) {
+ return elem.nodeValue;
+ }
+
+ // Do not include comment or processing instruction nodes
+
+ return ret;
+ },
+
+ // results is for internal usage only
+ makeArray: function( arr, results ) {
+ var ret = results || [];
+
+ if ( arr != null ) {
+ if ( isArrayLike( Object( arr ) ) ) {
+ jQuery.merge( ret,
+ typeof arr === "string" ?
+ [ arr ] : arr
+ );
+ } else {
+ push.call( ret, arr );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, arr, i ) {
+ return arr == null ? -1 : indexOf.call( arr, elem, i );
+ },
+
+ isXMLDoc: function( elem ) {
+ var namespace = elem && elem.namespaceURI,
+ docElem = elem && ( elem.ownerDocument || elem ).documentElement;
+
+ // Assume HTML when documentElement doesn't yet exist, such as inside
+ // document fragments.
+ return !rhtmlSuffix.test( namespace || docElem && docElem.nodeName || "HTML" );
+ },
+
+ // Support: Android <=4.0 only, PhantomJS 1 only
+ // push.apply(_, arraylike) throws on ancient WebKit
+ merge: function( first, second ) {
+ var len = +second.length,
+ j = 0,
+ i = first.length;
+
+ for ( ; j < len; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, invert ) {
+ var callbackInverse,
+ matches = [],
+ i = 0,
+ length = elems.length,
+ callbackExpect = !invert;
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( ; i < length; i++ ) {
+ callbackInverse = !callback( elems[ i ], i );
+ if ( callbackInverse !== callbackExpect ) {
+ matches.push( elems[ i ] );
+ }
+ }
+
+ return matches;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var length, value,
+ i = 0,
+ ret = [];
+
+ // Go through the array, translating each of the items to their new values
+ if ( isArrayLike( elems ) ) {
+ length = elems.length;
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret.push( value );
+ }
+ }
+
+ // Go through every key on the object,
+ } else {
+ for ( i in elems ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret.push( value );
+ }
+ }
+ }
+
+ // Flatten any nested arrays
+ return flat( ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ // jQuery.support is not used in Core but other projects attach their
+ // properties to it so it needs to exist.
+ support: support
+} );
+
+if ( typeof Symbol === "function" ) {
+ jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ];
+}
+
+// Populate the class2type map
+jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
+ function( _i, name ) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+ } );
+
+function isArrayLike( obj ) {
+
+ // Support: real iOS 8.2 only (not reproducible in simulator)
+ // `in` check used to prevent JIT error (gh-2145)
+ // hasOwn isn't used here due to false negatives
+ // regarding Nodelist length in IE
+ var length = !!obj && "length" in obj && obj.length,
+ type = toType( obj );
+
+ if ( isFunction( obj ) || isWindow( obj ) ) {
+ return false;
+ }
+
+ return type === "array" || length === 0 ||
+ typeof length === "number" && length > 0 && ( length - 1 ) in obj;
+}
+
+
+function nodeName( elem, name ) {
+
+ return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
+
+}
+var pop = arr.pop;
+
+
+var sort = arr.sort;
+
+
+var splice = arr.splice;
+
+
+var whitespace = "[\\x20\\t\\r\\n\\f]";
+
+
+var rtrimCSS = new RegExp(
+ "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$",
+ "g"
+);
+
+
+
+
+// Note: an element does not contain itself
+jQuery.contains = function( a, b ) {
+ var bup = b && b.parentNode;
+
+ return a === bup || !!( bup && bup.nodeType === 1 && (
+
+ // Support: IE 9 - 11+
+ // IE doesn't have `contains` on SVG.
+ a.contains ?
+ a.contains( bup ) :
+ a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
+ ) );
+};
+
+
+
+
+// CSS string/identifier serialization
+// https://drafts.csswg.org/cssom/#common-serializing-idioms
+var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g;
+
+function fcssescape( ch, asCodePoint ) {
+ if ( asCodePoint ) {
+
+ // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
+ if ( ch === "\0" ) {
+ return "\uFFFD";
+ }
+
+ // Control characters and (dependent upon position) numbers get escaped as code points
+ return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " ";
+ }
+
+ // Other potentially-special ASCII characters get backslash-escaped
+ return "\\" + ch;
+}
+
+jQuery.escapeSelector = function( sel ) {
+ return ( sel + "" ).replace( rcssescape, fcssescape );
+};
+
+
+
+
+var preferredDoc = document,
+ pushNative = push;
+
+( function() {
+
+var i,
+ Expr,
+ outermostContext,
+ sortInput,
+ hasDuplicate,
+ push = pushNative,
+
+ // Local document vars
+ document,
+ documentElement,
+ documentIsHTML,
+ rbuggyQSA,
+ matches,
+
+ // Instance-specific data
+ expando = jQuery.expando,
+ dirruns = 0,
+ done = 0,
+ classCache = createCache(),
+ tokenCache = createCache(),
+ compilerCache = createCache(),
+ nonnativeSelectorCache = createCache(),
+ sortOrder = function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
+ }
+ return 0;
+ },
+
+ booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|" +
+ "loop|multiple|open|readonly|required|scoped",
+
+ // Regular expressions
+
+ // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
+ identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace +
+ "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",
+
+ // Attribute selectors: https://www.w3.org/TR/selectors/#attribute-selectors
+ attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
+
+ // Operator (capture 2)
+ "*([*^$|!~]?=)" + whitespace +
+
+ // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
+ "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" +
+ whitespace + "*\\]",
+
+ pseudos = ":(" + identifier + ")(?:\\((" +
+
+ // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
+ // 1. quoted (capture 3; capture 4 or capture 5)
+ "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
+
+ // 2. simple (capture 6)
+ "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
+
+ // 3. anything else (capture 2)
+ ".*" +
+ ")\\)|)",
+
+ // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+ rwhitespace = new RegExp( whitespace + "+", "g" ),
+
+ rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
+ rleadingCombinator = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" +
+ whitespace + "*" ),
+ rdescend = new RegExp( whitespace + "|>" ),
+
+ rpseudo = new RegExp( pseudos ),
+ ridentifier = new RegExp( "^" + identifier + "$" ),
+
+ matchExpr = {
+ ID: new RegExp( "^#(" + identifier + ")" ),
+ CLASS: new RegExp( "^\\.(" + identifier + ")" ),
+ TAG: new RegExp( "^(" + identifier + "|[*])" ),
+ ATTR: new RegExp( "^" + attributes ),
+ PSEUDO: new RegExp( "^" + pseudos ),
+ CHILD: new RegExp(
+ "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" +
+ whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" +
+ whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
+ bool: new RegExp( "^(?:" + booleans + ")$", "i" ),
+
+ // For use in libraries implementing .is()
+ // We use this for POS matching in `select`
+ needsContext: new RegExp( "^" + whitespace +
+ "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace +
+ "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
+ },
+
+ rinputs = /^(?:input|select|textarea|button)$/i,
+ rheader = /^h\d$/i,
+
+ // Easily-parseable/retrievable ID or TAG or CLASS selectors
+ rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
+
+ rsibling = /[+~]/,
+
+ // CSS escapes
+ // https://www.w3.org/TR/CSS21/syndata.html#escaped-characters
+ runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace +
+ "?|\\\\([^\\r\\n\\f])", "g" ),
+ funescape = function( escape, nonHex ) {
+ var high = "0x" + escape.slice( 1 ) - 0x10000;
+
+ if ( nonHex ) {
+
+ // Strip the backslash prefix from a non-hex escape sequence
+ return nonHex;
+ }
+
+ // Replace a hexadecimal escape sequence with the encoded Unicode code point
+ // Support: IE <=11+
+ // For values outside the Basic Multilingual Plane (BMP), manually construct a
+ // surrogate pair
+ return high < 0 ?
+ String.fromCharCode( high + 0x10000 ) :
+ String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
+ },
+
+ // Used for iframes; see `setDocument`.
+ // Support: IE 9 - 11+, Edge 12 - 18+
+ // Removing the function wrapper causes a "Permission Denied"
+ // error in IE/Edge.
+ unloadHandler = function() {
+ setDocument();
+ },
+
+ inDisabledFieldset = addCombinator(
+ function( elem ) {
+ return elem.disabled === true && nodeName( elem, "fieldset" );
+ },
+ { dir: "parentNode", next: "legend" }
+ );
+
+// Support: IE <=9 only
+// Accessing document.activeElement can throw unexpectedly
+// https://bugs.jquery.com/ticket/13393
+function safeActiveElement() {
+ try {
+ return document.activeElement;
+ } catch ( err ) { }
+}
+
+// Optimize for push.apply( _, NodeList )
+try {
+ push.apply(
+ ( arr = slice.call( preferredDoc.childNodes ) ),
+ preferredDoc.childNodes
+ );
+
+ // Support: Android <=4.0
+ // Detect silently failing push.apply
+ // eslint-disable-next-line no-unused-expressions
+ arr[ preferredDoc.childNodes.length ].nodeType;
+} catch ( e ) {
+ push = {
+ apply: function( target, els ) {
+ pushNative.apply( target, slice.call( els ) );
+ },
+ call: function( target ) {
+ pushNative.apply( target, slice.call( arguments, 1 ) );
+ }
+ };
+}
+
+function find( selector, context, results, seed ) {
+ var m, i, elem, nid, match, groups, newSelector,
+ newContext = context && context.ownerDocument,
+
+ // nodeType defaults to 9, since context defaults to document
+ nodeType = context ? context.nodeType : 9;
+
+ results = results || [];
+
+ // Return early from calls with invalid selector or context
+ if ( typeof selector !== "string" || !selector ||
+ nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
+
+ return results;
+ }
+
+ // Try to shortcut find operations (as opposed to filters) in HTML documents
+ if ( !seed ) {
+ setDocument( context );
+ context = context || document;
+
+ if ( documentIsHTML ) {
+
+ // If the selector is sufficiently simple, try using a "get*By*" DOM method
+ // (excepting DocumentFragment context, where the methods don't exist)
+ if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) {
+
+ // ID selector
+ if ( ( m = match[ 1 ] ) ) {
+
+ // Document context
+ if ( nodeType === 9 ) {
+ if ( ( elem = context.getElementById( m ) ) ) {
+
+ // Support: IE 9 only
+ // getElementById can match elements by name instead of ID
+ if ( elem.id === m ) {
+ push.call( results, elem );
+ return results;
+ }
+ } else {
+ return results;
+ }
+
+ // Element context
+ } else {
+
+ // Support: IE 9 only
+ // getElementById can match elements by name instead of ID
+ if ( newContext && ( elem = newContext.getElementById( m ) ) &&
+ find.contains( context, elem ) &&
+ elem.id === m ) {
+
+ push.call( results, elem );
+ return results;
+ }
+ }
+
+ // Type selector
+ } else if ( match[ 2 ] ) {
+ push.apply( results, context.getElementsByTagName( selector ) );
+ return results;
+
+ // Class selector
+ } else if ( ( m = match[ 3 ] ) && context.getElementsByClassName ) {
+ push.apply( results, context.getElementsByClassName( m ) );
+ return results;
+ }
+ }
+
+ // Take advantage of querySelectorAll
+ if ( !nonnativeSelectorCache[ selector + " " ] &&
+ ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) ) {
+
+ newSelector = selector;
+ newContext = context;
+
+ // qSA considers elements outside a scoping root when evaluating child or
+ // descendant combinators, which is not what we want.
+ // In such cases, we work around the behavior by prefixing every selector in the
+ // list with an ID selector referencing the scope context.
+ // The technique has to be used as well when a leading combinator is used
+ // as such selectors are not recognized by querySelectorAll.
+ // Thanks to Andrew Dupont for this technique.
+ if ( nodeType === 1 &&
+ ( rdescend.test( selector ) || rleadingCombinator.test( selector ) ) ) {
+
+ // Expand context for sibling selectors
+ newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
+ context;
+
+ // We can use :scope instead of the ID hack if the browser
+ // supports it & if we're not changing the context.
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when
+ // strict-comparing two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( newContext != context || !support.scope ) {
+
+ // Capture the context ID, setting it first if necessary
+ if ( ( nid = context.getAttribute( "id" ) ) ) {
+ nid = jQuery.escapeSelector( nid );
+ } else {
+ context.setAttribute( "id", ( nid = expando ) );
+ }
+ }
+
+ // Prefix every selector in the list
+ groups = tokenize( selector );
+ i = groups.length;
+ while ( i-- ) {
+ groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " +
+ toSelector( groups[ i ] );
+ }
+ newSelector = groups.join( "," );
+ }
+
+ try {
+ push.apply( results,
+ newContext.querySelectorAll( newSelector )
+ );
+ return results;
+ } catch ( qsaError ) {
+ nonnativeSelectorCache( selector, true );
+ } finally {
+ if ( nid === expando ) {
+ context.removeAttribute( "id" );
+ }
+ }
+ }
+ }
+ }
+
+ // All others
+ return select( selector.replace( rtrimCSS, "$1" ), context, results, seed );
+}
+
+/**
+ * Create key-value caches of limited size
+ * @returns {function(string, object)} Returns the Object data after storing it on itself with
+ * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
+ * deleting the oldest entry
+ */
+function createCache() {
+ var keys = [];
+
+ function cache( key, value ) {
+
+ // Use (key + " ") to avoid collision with native prototype properties
+ // (see https://github.com/jquery/sizzle/issues/157)
+ if ( keys.push( key + " " ) > Expr.cacheLength ) {
+
+ // Only keep the most recent entries
+ delete cache[ keys.shift() ];
+ }
+ return ( cache[ key + " " ] = value );
+ }
+ return cache;
+}
+
+/**
+ * Mark a function for special use by jQuery selector module
+ * @param {Function} fn The function to mark
+ */
+function markFunction( fn ) {
+ fn[ expando ] = true;
+ return fn;
+}
+
+/**
+ * Support testing using an element
+ * @param {Function} fn Passed the created element and returns a boolean result
+ */
+function assert( fn ) {
+ var el = document.createElement( "fieldset" );
+
+ try {
+ return !!fn( el );
+ } catch ( e ) {
+ return false;
+ } finally {
+
+ // Remove from its parent by default
+ if ( el.parentNode ) {
+ el.parentNode.removeChild( el );
+ }
+
+ // release memory in IE
+ el = null;
+ }
+}
+
+/**
+ * Returns a function to use in pseudos for input types
+ * @param {String} type
+ */
+function createInputPseudo( type ) {
+ return function( elem ) {
+ return nodeName( elem, "input" ) && elem.type === type;
+ };
+}
+
+/**
+ * Returns a function to use in pseudos for buttons
+ * @param {String} type
+ */
+function createButtonPseudo( type ) {
+ return function( elem ) {
+ return ( nodeName( elem, "input" ) || nodeName( elem, "button" ) ) &&
+ elem.type === type;
+ };
+}
+
+/**
+ * Returns a function to use in pseudos for :enabled/:disabled
+ * @param {Boolean} disabled true for :disabled; false for :enabled
+ */
+function createDisabledPseudo( disabled ) {
+
+ // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable
+ return function( elem ) {
+
+ // Only certain elements can match :enabled or :disabled
+ // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled
+ // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
+ if ( "form" in elem ) {
+
+ // Check for inherited disabledness on relevant non-disabled elements:
+ // * listed form-associated elements in a disabled fieldset
+ // https://html.spec.whatwg.org/multipage/forms.html#category-listed
+ // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled
+ // * option elements in a disabled optgroup
+ // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled
+ // All such elements have a "form" property.
+ if ( elem.parentNode && elem.disabled === false ) {
+
+ // Option elements defer to a parent optgroup if present
+ if ( "label" in elem ) {
+ if ( "label" in elem.parentNode ) {
+ return elem.parentNode.disabled === disabled;
+ } else {
+ return elem.disabled === disabled;
+ }
+ }
+
+ // Support: IE 6 - 11+
+ // Use the isDisabled shortcut property to check for disabled fieldset ancestors
+ return elem.isDisabled === disabled ||
+
+ // Where there is no isDisabled, check manually
+ elem.isDisabled !== !disabled &&
+ inDisabledFieldset( elem ) === disabled;
+ }
+
+ return elem.disabled === disabled;
+
+ // Try to winnow out elements that can't be disabled before trusting the disabled property.
+ // Some victims get caught in our net (label, legend, menu, track), but it shouldn't
+ // even exist on them, let alone have a boolean value.
+ } else if ( "label" in elem ) {
+ return elem.disabled === disabled;
+ }
+
+ // Remaining elements are neither :enabled nor :disabled
+ return false;
+ };
+}
+
+/**
+ * Returns a function to use in pseudos for positionals
+ * @param {Function} fn
+ */
+function createPositionalPseudo( fn ) {
+ return markFunction( function( argument ) {
+ argument = +argument;
+ return markFunction( function( seed, matches ) {
+ var j,
+ matchIndexes = fn( [], seed.length, argument ),
+ i = matchIndexes.length;
+
+ // Match elements found at the specified indexes
+ while ( i-- ) {
+ if ( seed[ ( j = matchIndexes[ i ] ) ] ) {
+ seed[ j ] = !( matches[ j ] = seed[ j ] );
+ }
+ }
+ } );
+ } );
+}
+
+/**
+ * Checks a node for validity as a jQuery selector context
+ * @param {Element|Object=} context
+ * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
+ */
+function testContext( context ) {
+ return context && typeof context.getElementsByTagName !== "undefined" && context;
+}
+
+/**
+ * Sets document-related variables once based on the current document
+ * @param {Element|Object} [node] An element or document object to use to set the document
+ * @returns {Object} Returns the current document
+ */
+function setDocument( node ) {
+ var subWindow,
+ doc = node ? node.ownerDocument || node : preferredDoc;
+
+ // Return early if doc is invalid or already selected
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) {
+ return document;
+ }
+
+ // Update global variables
+ document = doc;
+ documentElement = document.documentElement;
+ documentIsHTML = !jQuery.isXMLDoc( document );
+
+ // Support: iOS 7 only, IE 9 - 11+
+ // Older browsers didn't support unprefixed `matches`.
+ matches = documentElement.matches ||
+ documentElement.webkitMatchesSelector ||
+ documentElement.msMatchesSelector;
+
+ // Support: IE 9 - 11+, Edge 12 - 18+
+ // Accessing iframe documents after unload throws "permission denied" errors
+ // (see trac-13936).
+ // Limit the fix to IE & Edge Legacy; despite Edge 15+ implementing `matches`,
+ // all IE 9+ and Edge Legacy versions implement `msMatchesSelector` as well.
+ if ( documentElement.msMatchesSelector &&
+
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ preferredDoc != document &&
+ ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) {
+
+ // Support: IE 9 - 11+, Edge 12 - 18+
+ subWindow.addEventListener( "unload", unloadHandler );
+ }
+
+ // Support: IE <10
+ // Check if getElementById returns elements by name
+ // The broken getElementById methods don't pick up programmatically-set names,
+ // so use a roundabout getElementsByName test
+ support.getById = assert( function( el ) {
+ documentElement.appendChild( el ).id = jQuery.expando;
+ return !document.getElementsByName ||
+ !document.getElementsByName( jQuery.expando ).length;
+ } );
+
+ // Support: IE 9 only
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node.
+ support.disconnectedMatch = assert( function( el ) {
+ return matches.call( el, "*" );
+ } );
+
+ // Support: IE 9 - 11+, Edge 12 - 18+
+ // IE/Edge don't support the :scope pseudo-class.
+ support.scope = assert( function() {
+ return document.querySelectorAll( ":scope" );
+ } );
+
+ // Support: Chrome 105 - 111 only, Safari 15.4 - 16.3 only
+ // Make sure the `:has()` argument is parsed unforgivingly.
+ // We include `*` in the test to detect buggy implementations that are
+ // _selectively_ forgiving (specifically when the list includes at least
+ // one valid selector).
+ // Note that we treat complete lack of support for `:has()` as if it were
+ // spec-compliant support, which is fine because use of `:has()` in such
+ // environments will fail in the qSA path and fall back to jQuery traversal
+ // anyway.
+ support.cssHas = assert( function() {
+ try {
+ document.querySelector( ":has(*,:jqfake)" );
+ return false;
+ } catch ( e ) {
+ return true;
+ }
+ } );
+
+ // ID filter and find
+ if ( support.getById ) {
+ Expr.filter.ID = function( id ) {
+ var attrId = id.replace( runescape, funescape );
+ return function( elem ) {
+ return elem.getAttribute( "id" ) === attrId;
+ };
+ };
+ Expr.find.ID = function( id, context ) {
+ if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
+ var elem = context.getElementById( id );
+ return elem ? [ elem ] : [];
+ }
+ };
+ } else {
+ Expr.filter.ID = function( id ) {
+ var attrId = id.replace( runescape, funescape );
+ return function( elem ) {
+ var node = typeof elem.getAttributeNode !== "undefined" &&
+ elem.getAttributeNode( "id" );
+ return node && node.value === attrId;
+ };
+ };
+
+ // Support: IE 6 - 7 only
+ // getElementById is not reliable as a find shortcut
+ Expr.find.ID = function( id, context ) {
+ if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
+ var node, i, elems,
+ elem = context.getElementById( id );
+
+ if ( elem ) {
+
+ // Verify the id attribute
+ node = elem.getAttributeNode( "id" );
+ if ( node && node.value === id ) {
+ return [ elem ];
+ }
+
+ // Fall back on getElementsByName
+ elems = context.getElementsByName( id );
+ i = 0;
+ while ( ( elem = elems[ i++ ] ) ) {
+ node = elem.getAttributeNode( "id" );
+ if ( node && node.value === id ) {
+ return [ elem ];
+ }
+ }
+ }
+
+ return [];
+ }
+ };
+ }
+
+ // Tag
+ Expr.find.TAG = function( tag, context ) {
+ if ( typeof context.getElementsByTagName !== "undefined" ) {
+ return context.getElementsByTagName( tag );
+
+ // DocumentFragment nodes don't have gEBTN
+ } else {
+ return context.querySelectorAll( tag );
+ }
+ };
+
+ // Class
+ Expr.find.CLASS = function( className, context ) {
+ if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) {
+ return context.getElementsByClassName( className );
+ }
+ };
+
+ /* QSA/matchesSelector
+ ---------------------------------------------------------------------- */
+
+ // QSA and matchesSelector support
+
+ rbuggyQSA = [];
+
+ // Build QSA regex
+ // Regex strategy adopted from Diego Perini
+ assert( function( el ) {
+
+ var input;
+
+ documentElement.appendChild( el ).innerHTML =
+ "<a id='" + expando + "' href='' disabled='disabled'></a>" +
+ "<select id='" + expando + "-\r\\' disabled='disabled'>" +
+ "<option selected=''></option></select>";
+
+ // Support: iOS <=7 - 8 only
+ // Boolean attributes and "value" are not treated correctly in some XML documents
+ if ( !el.querySelectorAll( "[selected]" ).length ) {
+ rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
+ }
+
+ // Support: iOS <=7 - 8 only
+ if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
+ rbuggyQSA.push( "~=" );
+ }
+
+ // Support: iOS 8 only
+ // https://bugs.webkit.org/show_bug.cgi?id=136851
+ // In-page `selector#id sibling-combinator selector` fails
+ if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) {
+ rbuggyQSA.push( ".#.+[+~]" );
+ }
+
+ // Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+
+ // In some of the document kinds, these selectors wouldn't work natively.
+ // This is probably OK but for backwards compatibility we want to maintain
+ // handling them through jQuery traversal in jQuery 3.x.
+ if ( !el.querySelectorAll( ":checked" ).length ) {
+ rbuggyQSA.push( ":checked" );
+ }
+
+ // Support: Windows 8 Native Apps
+ // The type and name attributes are restricted during .innerHTML assignment
+ input = document.createElement( "input" );
+ input.setAttribute( "type", "hidden" );
+ el.appendChild( input ).setAttribute( "name", "D" );
+
+ // Support: IE 9 - 11+
+ // IE's :disabled selector does not pick up the children of disabled fieldsets
+ // Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+
+ // In some of the document kinds, these selectors wouldn't work natively.
+ // This is probably OK but for backwards compatibility we want to maintain
+ // handling them through jQuery traversal in jQuery 3.x.
+ documentElement.appendChild( el ).disabled = true;
+ if ( el.querySelectorAll( ":disabled" ).length !== 2 ) {
+ rbuggyQSA.push( ":enabled", ":disabled" );
+ }
+
+ // Support: IE 11+, Edge 15 - 18+
+ // IE 11/Edge don't find elements on a `[name='']` query in some cases.
+ // Adding a temporary attribute to the document before the selection works
+ // around the issue.
+ // Interestingly, IE 10 & older don't seem to have the issue.
+ input = document.createElement( "input" );
+ input.setAttribute( "name", "" );
+ el.appendChild( input );
+ if ( !el.querySelectorAll( "[name='']" ).length ) {
+ rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" +
+ whitespace + "*(?:''|\"\")" );
+ }
+ } );
+
+ if ( !support.cssHas ) {
+
+ // Support: Chrome 105 - 110+, Safari 15.4 - 16.3+
+ // Our regular `try-catch` mechanism fails to detect natively-unsupported
+ // pseudo-classes inside `:has()` (such as `:has(:contains("Foo"))`)
+ // in browsers that parse the `:has()` argument as a forgiving selector list.
+ // https://drafts.csswg.org/selectors/#relational now requires the argument
+ // to be parsed unforgivingly, but browsers have not yet fully adjusted.
+ rbuggyQSA.push( ":has" );
+ }
+
+ rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) );
+
+ /* Sorting
+ ---------------------------------------------------------------------- */
+
+ // Document order sorting
+ sortOrder = function( a, b ) {
+
+ // Flag for duplicate removal
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ // Sort on method existence if only one input has compareDocumentPosition
+ var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
+ if ( compare ) {
+ return compare;
+ }
+
+ // Calculate position if both inputs belong to the same document
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ?
+ a.compareDocumentPosition( b ) :
+
+ // Otherwise we know they are disconnected
+ 1;
+
+ // Disconnected nodes
+ if ( compare & 1 ||
+ ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) {
+
+ // Choose the first element that is related to our preferred document
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( a === document || a.ownerDocument == preferredDoc &&
+ find.contains( preferredDoc, a ) ) {
+ return -1;
+ }
+
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( b === document || b.ownerDocument == preferredDoc &&
+ find.contains( preferredDoc, b ) ) {
+ return 1;
+ }
+
+ // Maintain original order
+ return sortInput ?
+ ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
+ 0;
+ }
+
+ return compare & 4 ? -1 : 1;
+ };
+
+ return document;
+}
+
+find.matches = function( expr, elements ) {
+ return find( expr, null, null, elements );
+};
+
+find.matchesSelector = function( elem, expr ) {
+ setDocument( elem );
+
+ if ( documentIsHTML &&
+ !nonnativeSelectorCache[ expr + " " ] &&
+ ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) {
+
+ try {
+ var ret = matches.call( elem, expr );
+
+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || support.disconnectedMatch ||
+
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9
+ elem.document && elem.document.nodeType !== 11 ) {
+ return ret;
+ }
+ } catch ( e ) {
+ nonnativeSelectorCache( expr, true );
+ }
+ }
+
+ return find( expr, document, null, [ elem ] ).length > 0;
+};
+
+find.contains = function( context, elem ) {
+
+ // Set document vars if needed
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( ( context.ownerDocument || context ) != document ) {
+ setDocument( context );
+ }
+ return jQuery.contains( context, elem );
+};
+
+
+find.attr = function( elem, name ) {
+
+ // Set document vars if needed
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( ( elem.ownerDocument || elem ) != document ) {
+ setDocument( elem );
+ }
+
+ var fn = Expr.attrHandle[ name.toLowerCase() ],
+
+ // Don't get fooled by Object.prototype properties (see trac-13807)
+ val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
+ fn( elem, name, !documentIsHTML ) :
+ undefined;
+
+ if ( val !== undefined ) {
+ return val;
+ }
+
+ return elem.getAttribute( name );
+};
+
+find.error = function( msg ) {
+ throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+/**
+ * Document sorting and removing duplicates
+ * @param {ArrayLike} results
+ */
+jQuery.uniqueSort = function( results ) {
+ var elem,
+ duplicates = [],
+ j = 0,
+ i = 0;
+
+ // Unless we *know* we can detect duplicates, assume their presence
+ //
+ // Support: Android <=4.0+
+ // Testing for detecting duplicates is unpredictable so instead assume we can't
+ // depend on duplicate detection in all browsers without a stable sort.
+ hasDuplicate = !support.sortStable;
+ sortInput = !support.sortStable && slice.call( results, 0 );
+ sort.call( results, sortOrder );
+
+ if ( hasDuplicate ) {
+ while ( ( elem = results[ i++ ] ) ) {
+ if ( elem === results[ i ] ) {
+ j = duplicates.push( i );
+ }
+ }
+ while ( j-- ) {
+ splice.call( results, duplicates[ j ], 1 );
+ }
+ }
+
+ // Clear input after sorting to release objects
+ // See https://github.com/jquery/sizzle/pull/225
+ sortInput = null;
+
+ return results;
+};
+
+jQuery.fn.uniqueSort = function() {
+ return this.pushStack( jQuery.uniqueSort( slice.apply( this ) ) );
+};
+
+Expr = jQuery.expr = {
+
+ // Can be adjusted by the user
+ cacheLength: 50,
+
+ createPseudo: markFunction,
+
+ match: matchExpr,
+
+ attrHandle: {},
+
+ find: {},
+
+ relative: {
+ ">": { dir: "parentNode", first: true },
+ " ": { dir: "parentNode" },
+ "+": { dir: "previousSibling", first: true },
+ "~": { dir: "previousSibling" }
+ },
+
+ preFilter: {
+ ATTR: function( match ) {
+ match[ 1 ] = match[ 1 ].replace( runescape, funescape );
+
+ // Move the given value to match[3] whether quoted or unquoted
+ match[ 3 ] = ( match[ 3 ] || match[ 4 ] || match[ 5 ] || "" )
+ .replace( runescape, funescape );
+
+ if ( match[ 2 ] === "~=" ) {
+ match[ 3 ] = " " + match[ 3 ] + " ";
+ }
+
+ return match.slice( 0, 4 );
+ },
+
+ CHILD: function( match ) {
+
+ /* matches from matchExpr["CHILD"]
+ 1 type (only|nth|...)
+ 2 what (child|of-type)
+ 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
+ 4 xn-component of xn+y argument ([+-]?\d*n|)
+ 5 sign of xn-component
+ 6 x of xn-component
+ 7 sign of y-component
+ 8 y of y-component
+ */
+ match[ 1 ] = match[ 1 ].toLowerCase();
+
+ if ( match[ 1 ].slice( 0, 3 ) === "nth" ) {
+
+ // nth-* requires argument
+ if ( !match[ 3 ] ) {
+ find.error( match[ 0 ] );
+ }
+
+ // numeric x and y parameters for Expr.filter.CHILD
+ // remember that false/true cast respectively to 0/1
+ match[ 4 ] = +( match[ 4 ] ?
+ match[ 5 ] + ( match[ 6 ] || 1 ) :
+ 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" )
+ );
+ match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" );
+
+ // other types prohibit arguments
+ } else if ( match[ 3 ] ) {
+ find.error( match[ 0 ] );
+ }
+
+ return match;
+ },
+
+ PSEUDO: function( match ) {
+ var excess,
+ unquoted = !match[ 6 ] && match[ 2 ];
+
+ if ( matchExpr.CHILD.test( match[ 0 ] ) ) {
+ return null;
+ }
+
+ // Accept quoted arguments as-is
+ if ( match[ 3 ] ) {
+ match[ 2 ] = match[ 4 ] || match[ 5 ] || "";
+
+ // Strip excess characters from unquoted arguments
+ } else if ( unquoted && rpseudo.test( unquoted ) &&
+
+ // Get excess from tokenize (recursively)
+ ( excess = tokenize( unquoted, true ) ) &&
+
+ // advance to the next closing parenthesis
+ ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) {
+
+ // excess is a negative index
+ match[ 0 ] = match[ 0 ].slice( 0, excess );
+ match[ 2 ] = unquoted.slice( 0, excess );
+ }
+
+ // Return only captures needed by the pseudo filter method (type and argument)
+ return match.slice( 0, 3 );
+ }
+ },
+
+ filter: {
+
+ TAG: function( nodeNameSelector ) {
+ var expectedNodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
+ return nodeNameSelector === "*" ?
+ function() {
+ return true;
+ } :
+ function( elem ) {
+ return nodeName( elem, expectedNodeName );
+ };
+ },
+
+ CLASS: function( className ) {
+ var pattern = classCache[ className + " " ];
+
+ return pattern ||
+ ( pattern = new RegExp( "(^|" + whitespace + ")" + className +
+ "(" + whitespace + "|$)" ) ) &&
+ classCache( className, function( elem ) {
+ return pattern.test(
+ typeof elem.className === "string" && elem.className ||
+ typeof elem.getAttribute !== "undefined" &&
+ elem.getAttribute( "class" ) ||
+ ""
+ );
+ } );
+ },
+
+ ATTR: function( name, operator, check ) {
+ return function( elem ) {
+ var result = find.attr( elem, name );
+
+ if ( result == null ) {
+ return operator === "!=";
+ }
+ if ( !operator ) {
+ return true;
+ }
+
+ result += "";
+
+ if ( operator === "=" ) {
+ return result === check;
+ }
+ if ( operator === "!=" ) {
+ return result !== check;
+ }
+ if ( operator === "^=" ) {
+ return check && result.indexOf( check ) === 0;
+ }
+ if ( operator === "*=" ) {
+ return check && result.indexOf( check ) > -1;
+ }
+ if ( operator === "$=" ) {
+ return check && result.slice( -check.length ) === check;
+ }
+ if ( operator === "~=" ) {
+ return ( " " + result.replace( rwhitespace, " " ) + " " )
+ .indexOf( check ) > -1;
+ }
+ if ( operator === "|=" ) {
+ return result === check || result.slice( 0, check.length + 1 ) === check + "-";
+ }
+
+ return false;
+ };
+ },
+
+ CHILD: function( type, what, _argument, first, last ) {
+ var simple = type.slice( 0, 3 ) !== "nth",
+ forward = type.slice( -4 ) !== "last",
+ ofType = what === "of-type";
+
+ return first === 1 && last === 0 ?
+
+ // Shortcut for :nth-*(n)
+ function( elem ) {
+ return !!elem.parentNode;
+ } :
+
+ function( elem, _context, xml ) {
+ var cache, outerCache, node, nodeIndex, start,
+ dir = simple !== forward ? "nextSibling" : "previousSibling",
+ parent = elem.parentNode,
+ name = ofType && elem.nodeName.toLowerCase(),
+ useCache = !xml && !ofType,
+ diff = false;
+
+ if ( parent ) {
+
+ // :(first|last|only)-(child|of-type)
+ if ( simple ) {
+ while ( dir ) {
+ node = elem;
+ while ( ( node = node[ dir ] ) ) {
+ if ( ofType ?
+ nodeName( node, name ) :
+ node.nodeType === 1 ) {
+
+ return false;
+ }
+ }
+
+ // Reverse direction for :only-* (if we haven't yet done so)
+ start = dir = type === "only" && !start && "nextSibling";
+ }
+ return true;
+ }
+
+ start = [ forward ? parent.firstChild : parent.lastChild ];
+
+ // non-xml :nth-child(...) stores cache data on `parent`
+ if ( forward && useCache ) {
+
+ // Seek `elem` from a previously-cached index
+ outerCache = parent[ expando ] || ( parent[ expando ] = {} );
+ cache = outerCache[ type ] || [];
+ nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
+ diff = nodeIndex && cache[ 2 ];
+ node = nodeIndex && parent.childNodes[ nodeIndex ];
+
+ while ( ( node = ++nodeIndex && node && node[ dir ] ||
+
+ // Fallback to seeking `elem` from the start
+ ( diff = nodeIndex = 0 ) || start.pop() ) ) {
+
+ // When found, cache indexes on `parent` and break
+ if ( node.nodeType === 1 && ++diff && node === elem ) {
+ outerCache[ type ] = [ dirruns, nodeIndex, diff ];
+ break;
+ }
+ }
+
+ } else {
+
+ // Use previously-cached element index if available
+ if ( useCache ) {
+ outerCache = elem[ expando ] || ( elem[ expando ] = {} );
+ cache = outerCache[ type ] || [];
+ nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
+ diff = nodeIndex;
+ }
+
+ // xml :nth-child(...)
+ // or :nth-last-child(...) or :nth(-last)?-of-type(...)
+ if ( diff === false ) {
+
+ // Use the same loop as above to seek `elem` from the start
+ while ( ( node = ++nodeIndex && node && node[ dir ] ||
+ ( diff = nodeIndex = 0 ) || start.pop() ) ) {
+
+ if ( ( ofType ?
+ nodeName( node, name ) :
+ node.nodeType === 1 ) &&
+ ++diff ) {
+
+ // Cache the index of each encountered element
+ if ( useCache ) {
+ outerCache = node[ expando ] ||
+ ( node[ expando ] = {} );
+ outerCache[ type ] = [ dirruns, diff ];
+ }
+
+ if ( node === elem ) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Incorporate the offset, then check against cycle size
+ diff -= last;
+ return diff === first || ( diff % first === 0 && diff / first >= 0 );
+ }
+ };
+ },
+
+ PSEUDO: function( pseudo, argument ) {
+
+ // pseudo-class names are case-insensitive
+ // https://www.w3.org/TR/selectors/#pseudo-classes
+ // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
+ // Remember that setFilters inherits from pseudos
+ var args,
+ fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
+ find.error( "unsupported pseudo: " + pseudo );
+
+ // The user may use createPseudo to indicate that
+ // arguments are needed to create the filter function
+ // just as jQuery does
+ if ( fn[ expando ] ) {
+ return fn( argument );
+ }
+
+ // But maintain support for old signatures
+ if ( fn.length > 1 ) {
+ args = [ pseudo, pseudo, "", argument ];
+ return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
+ markFunction( function( seed, matches ) {
+ var idx,
+ matched = fn( seed, argument ),
+ i = matched.length;
+ while ( i-- ) {
+ idx = indexOf.call( seed, matched[ i ] );
+ seed[ idx ] = !( matches[ idx ] = matched[ i ] );
+ }
+ } ) :
+ function( elem ) {
+ return fn( elem, 0, args );
+ };
+ }
+
+ return fn;
+ }
+ },
+
+ pseudos: {
+
+ // Potentially complex pseudos
+ not: markFunction( function( selector ) {
+
+ // Trim the selector passed to compile
+ // to avoid treating leading and trailing
+ // spaces as combinators
+ var input = [],
+ results = [],
+ matcher = compile( selector.replace( rtrimCSS, "$1" ) );
+
+ return matcher[ expando ] ?
+ markFunction( function( seed, matches, _context, xml ) {
+ var elem,
+ unmatched = matcher( seed, null, xml, [] ),
+ i = seed.length;
+
+ // Match elements unmatched by `matcher`
+ while ( i-- ) {
+ if ( ( elem = unmatched[ i ] ) ) {
+ seed[ i ] = !( matches[ i ] = elem );
+ }
+ }
+ } ) :
+ function( elem, _context, xml ) {
+ input[ 0 ] = elem;
+ matcher( input, null, xml, results );
+
+ // Don't keep the element
+ // (see https://github.com/jquery/sizzle/issues/299)
+ input[ 0 ] = null;
+ return !results.pop();
+ };
+ } ),
+
+ has: markFunction( function( selector ) {
+ return function( elem ) {
+ return find( selector, elem ).length > 0;
+ };
+ } ),
+
+ contains: markFunction( function( text ) {
+ text = text.replace( runescape, funescape );
+ return function( elem ) {
+ return ( elem.textContent || jQuery.text( elem ) ).indexOf( text ) > -1;
+ };
+ } ),
+
+ // "Whether an element is represented by a :lang() selector
+ // is based solely on the element's language value
+ // being equal to the identifier C,
+ // or beginning with the identifier C immediately followed by "-".
+ // The matching of C against the element's language value is performed case-insensitively.
+ // The identifier C does not have to be a valid language name."
+ // https://www.w3.org/TR/selectors/#lang-pseudo
+ lang: markFunction( function( lang ) {
+
+ // lang value must be a valid identifier
+ if ( !ridentifier.test( lang || "" ) ) {
+ find.error( "unsupported lang: " + lang );
+ }
+ lang = lang.replace( runescape, funescape ).toLowerCase();
+ return function( elem ) {
+ var elemLang;
+ do {
+ if ( ( elemLang = documentIsHTML ?
+ elem.lang :
+ elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) {
+
+ elemLang = elemLang.toLowerCase();
+ return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
+ }
+ } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 );
+ return false;
+ };
+ } ),
+
+ // Miscellaneous
+ target: function( elem ) {
+ var hash = window.location && window.location.hash;
+ return hash && hash.slice( 1 ) === elem.id;
+ },
+
+ root: function( elem ) {
+ return elem === documentElement;
+ },
+
+ focus: function( elem ) {
+ return elem === safeActiveElement() &&
+ document.hasFocus() &&
+ !!( elem.type || elem.href || ~elem.tabIndex );
+ },
+
+ // Boolean properties
+ enabled: createDisabledPseudo( false ),
+ disabled: createDisabledPseudo( true ),
+
+ checked: function( elem ) {
+
+ // In CSS3, :checked should return both checked and selected elements
+ // https://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ return ( nodeName( elem, "input" ) && !!elem.checked ) ||
+ ( nodeName( elem, "option" ) && !!elem.selected );
+ },
+
+ selected: function( elem ) {
+
+ // Support: IE <=11+
+ // Accessing the selectedIndex property
+ // forces the browser to treat the default option as
+ // selected when in an optgroup.
+ if ( elem.parentNode ) {
+ // eslint-disable-next-line no-unused-expressions
+ elem.parentNode.selectedIndex;
+ }
+
+ return elem.selected === true;
+ },
+
+ // Contents
+ empty: function( elem ) {
+
+ // https://www.w3.org/TR/selectors/#empty-pseudo
+ // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
+ // but not by others (comment: 8; processing instruction: 7; etc.)
+ // nodeType < 6 works because attributes (2) do not appear as children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ if ( elem.nodeType < 6 ) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ parent: function( elem ) {
+ return !Expr.pseudos.empty( elem );
+ },
+
+ // Element/input types
+ header: function( elem ) {
+ return rheader.test( elem.nodeName );
+ },
+
+ input: function( elem ) {
+ return rinputs.test( elem.nodeName );
+ },
+
+ button: function( elem ) {
+ return nodeName( elem, "input" ) && elem.type === "button" ||
+ nodeName( elem, "button" );
+ },
+
+ text: function( elem ) {
+ var attr;
+ return nodeName( elem, "input" ) && elem.type === "text" &&
+
+ // Support: IE <10 only
+ // New HTML5 attribute values (e.g., "search") appear
+ // with elem.type === "text"
+ ( ( attr = elem.getAttribute( "type" ) ) == null ||
+ attr.toLowerCase() === "text" );
+ },
+
+ // Position-in-collection
+ first: createPositionalPseudo( function() {
+ return [ 0 ];
+ } ),
+
+ last: createPositionalPseudo( function( _matchIndexes, length ) {
+ return [ length - 1 ];
+ } ),
+
+ eq: createPositionalPseudo( function( _matchIndexes, length, argument ) {
+ return [ argument < 0 ? argument + length : argument ];
+ } ),
+
+ even: createPositionalPseudo( function( matchIndexes, length ) {
+ var i = 0;
+ for ( ; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ } ),
+
+ odd: createPositionalPseudo( function( matchIndexes, length ) {
+ var i = 1;
+ for ( ; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ } ),
+
+ lt: createPositionalPseudo( function( matchIndexes, length, argument ) {
+ var i;
+
+ if ( argument < 0 ) {
+ i = argument + length;
+ } else if ( argument > length ) {
+ i = length;
+ } else {
+ i = argument;
+ }
+
+ for ( ; --i >= 0; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ } ),
+
+ gt: createPositionalPseudo( function( matchIndexes, length, argument ) {
+ var i = argument < 0 ? argument + length : argument;
+ for ( ; ++i < length; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ } )
+ }
+};
+
+Expr.pseudos.nth = Expr.pseudos.eq;
+
+// Add button/input type pseudos
+for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
+ Expr.pseudos[ i ] = createInputPseudo( i );
+}
+for ( i in { submit: true, reset: true } ) {
+ Expr.pseudos[ i ] = createButtonPseudo( i );
+}
+
+// Easy API for creating new setFilters
+function setFilters() {}
+setFilters.prototype = Expr.filters = Expr.pseudos;
+Expr.setFilters = new setFilters();
+
+function tokenize( selector, parseOnly ) {
+ var matched, match, tokens, type,
+ soFar, groups, preFilters,
+ cached = tokenCache[ selector + " " ];
+
+ if ( cached ) {
+ return parseOnly ? 0 : cached.slice( 0 );
+ }
+
+ soFar = selector;
+ groups = [];
+ preFilters = Expr.preFilter;
+
+ while ( soFar ) {
+
+ // Comma and first run
+ if ( !matched || ( match = rcomma.exec( soFar ) ) ) {
+ if ( match ) {
+
+ // Don't consume trailing commas as valid
+ soFar = soFar.slice( match[ 0 ].length ) || soFar;
+ }
+ groups.push( ( tokens = [] ) );
+ }
+
+ matched = false;
+
+ // Combinators
+ if ( ( match = rleadingCombinator.exec( soFar ) ) ) {
+ matched = match.shift();
+ tokens.push( {
+ value: matched,
+
+ // Cast descendant combinators to space
+ type: match[ 0 ].replace( rtrimCSS, " " )
+ } );
+ soFar = soFar.slice( matched.length );
+ }
+
+ // Filters
+ for ( type in Expr.filter ) {
+ if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] ||
+ ( match = preFilters[ type ]( match ) ) ) ) {
+ matched = match.shift();
+ tokens.push( {
+ value: matched,
+ type: type,
+ matches: match
+ } );
+ soFar = soFar.slice( matched.length );
+ }
+ }
+
+ if ( !matched ) {
+ break;
+ }
+ }
+
+ // Return the length of the invalid excess
+ // if we're just parsing
+ // Otherwise, throw an error or return tokens
+ if ( parseOnly ) {
+ return soFar.length;
+ }
+
+ return soFar ?
+ find.error( selector ) :
+
+ // Cache the tokens
+ tokenCache( selector, groups ).slice( 0 );
+}
+
+function toSelector( tokens ) {
+ var i = 0,
+ len = tokens.length,
+ selector = "";
+ for ( ; i < len; i++ ) {
+ selector += tokens[ i ].value;
+ }
+ return selector;
+}
+
+function addCombinator( matcher, combinator, base ) {
+ var dir = combinator.dir,
+ skip = combinator.next,
+ key = skip || dir,
+ checkNonElements = base && key === "parentNode",
+ doneName = done++;
+
+ return combinator.first ?
+
+ // Check against closest ancestor/preceding element
+ function( elem, context, xml ) {
+ while ( ( elem = elem[ dir ] ) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ return matcher( elem, context, xml );
+ }
+ }
+ return false;
+ } :
+
+ // Check against all ancestor/preceding elements
+ function( elem, context, xml ) {
+ var oldCache, outerCache,
+ newCache = [ dirruns, doneName ];
+
+ // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching
+ if ( xml ) {
+ while ( ( elem = elem[ dir ] ) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ if ( matcher( elem, context, xml ) ) {
+ return true;
+ }
+ }
+ }
+ } else {
+ while ( ( elem = elem[ dir ] ) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ outerCache = elem[ expando ] || ( elem[ expando ] = {} );
+
+ if ( skip && nodeName( elem, skip ) ) {
+ elem = elem[ dir ] || elem;
+ } else if ( ( oldCache = outerCache[ key ] ) &&
+ oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
+
+ // Assign to newCache so results back-propagate to previous elements
+ return ( newCache[ 2 ] = oldCache[ 2 ] );
+ } else {
+
+ // Reuse newcache so results back-propagate to previous elements
+ outerCache[ key ] = newCache;
+
+ // A match means we're done; a fail means we have to keep checking
+ if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ };
+}
+
+function elementMatcher( matchers ) {
+ return matchers.length > 1 ?
+ function( elem, context, xml ) {
+ var i = matchers.length;
+ while ( i-- ) {
+ if ( !matchers[ i ]( elem, context, xml ) ) {
+ return false;
+ }
+ }
+ return true;
+ } :
+ matchers[ 0 ];
+}
+
+function multipleContexts( selector, contexts, results ) {
+ var i = 0,
+ len = contexts.length;
+ for ( ; i < len; i++ ) {
+ find( selector, contexts[ i ], results );
+ }
+ return results;
+}
+
+function condense( unmatched, map, filter, context, xml ) {
+ var elem,
+ newUnmatched = [],
+ i = 0,
+ len = unmatched.length,
+ mapped = map != null;
+
+ for ( ; i < len; i++ ) {
+ if ( ( elem = unmatched[ i ] ) ) {
+ if ( !filter || filter( elem, context, xml ) ) {
+ newUnmatched.push( elem );
+ if ( mapped ) {
+ map.push( i );
+ }
+ }
+ }
+ }
+
+ return newUnmatched;
+}
+
+function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
+ if ( postFilter && !postFilter[ expando ] ) {
+ postFilter = setMatcher( postFilter );
+ }
+ if ( postFinder && !postFinder[ expando ] ) {
+ postFinder = setMatcher( postFinder, postSelector );
+ }
+ return markFunction( function( seed, results, context, xml ) {
+ var temp, i, elem, matcherOut,
+ preMap = [],
+ postMap = [],
+ preexisting = results.length,
+
+ // Get initial elements from seed or context
+ elems = seed ||
+ multipleContexts( selector || "*",
+ context.nodeType ? [ context ] : context, [] ),
+
+ // Prefilter to get matcher input, preserving a map for seed-results synchronization
+ matcherIn = preFilter && ( seed || !selector ) ?
+ condense( elems, preMap, preFilter, context, xml ) :
+ elems;
+
+ if ( matcher ) {
+
+ // If we have a postFinder, or filtered seed, or non-seed postFilter
+ // or preexisting results,
+ matcherOut = postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
+
+ // ...intermediate processing is necessary
+ [] :
+
+ // ...otherwise use results directly
+ results;
+
+ // Find primary matches
+ matcher( matcherIn, matcherOut, context, xml );
+ } else {
+ matcherOut = matcherIn;
+ }
+
+ // Apply postFilter
+ if ( postFilter ) {
+ temp = condense( matcherOut, postMap );
+ postFilter( temp, [], context, xml );
+
+ // Un-match failing elements by moving them back to matcherIn
+ i = temp.length;
+ while ( i-- ) {
+ if ( ( elem = temp[ i ] ) ) {
+ matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem );
+ }
+ }
+ }
+
+ if ( seed ) {
+ if ( postFinder || preFilter ) {
+ if ( postFinder ) {
+
+ // Get the final matcherOut by condensing this intermediate into postFinder contexts
+ temp = [];
+ i = matcherOut.length;
+ while ( i-- ) {
+ if ( ( elem = matcherOut[ i ] ) ) {
+
+ // Restore matcherIn since elem is not yet a final match
+ temp.push( ( matcherIn[ i ] = elem ) );
+ }
+ }
+ postFinder( null, ( matcherOut = [] ), temp, xml );
+ }
+
+ // Move matched elements from seed to results to keep them synchronized
+ i = matcherOut.length;
+ while ( i-- ) {
+ if ( ( elem = matcherOut[ i ] ) &&
+ ( temp = postFinder ? indexOf.call( seed, elem ) : preMap[ i ] ) > -1 ) {
+
+ seed[ temp ] = !( results[ temp ] = elem );
+ }
+ }
+ }
+
+ // Add elements to results, through postFinder if defined
+ } else {
+ matcherOut = condense(
+ matcherOut === results ?
+ matcherOut.splice( preexisting, matcherOut.length ) :
+ matcherOut
+ );
+ if ( postFinder ) {
+ postFinder( null, results, matcherOut, xml );
+ } else {
+ push.apply( results, matcherOut );
+ }
+ }
+ } );
+}
+
+function matcherFromTokens( tokens ) {
+ var checkContext, matcher, j,
+ len = tokens.length,
+ leadingRelative = Expr.relative[ tokens[ 0 ].type ],
+ implicitRelative = leadingRelative || Expr.relative[ " " ],
+ i = leadingRelative ? 1 : 0,
+
+ // The foundational matcher ensures that elements are reachable from top-level context(s)
+ matchContext = addCombinator( function( elem ) {
+ return elem === checkContext;
+ }, implicitRelative, true ),
+ matchAnyContext = addCombinator( function( elem ) {
+ return indexOf.call( checkContext, elem ) > -1;
+ }, implicitRelative, true ),
+ matchers = [ function( elem, context, xml ) {
+
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ var ret = ( !leadingRelative && ( xml || context != outermostContext ) ) || (
+ ( checkContext = context ).nodeType ?
+ matchContext( elem, context, xml ) :
+ matchAnyContext( elem, context, xml ) );
+
+ // Avoid hanging onto element
+ // (see https://github.com/jquery/sizzle/issues/299)
+ checkContext = null;
+ return ret;
+ } ];
+
+ for ( ; i < len; i++ ) {
+ if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) {
+ matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ];
+ } else {
+ matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches );
+
+ // Return special upon seeing a positional matcher
+ if ( matcher[ expando ] ) {
+
+ // Find the next relative operator (if any) for proper handling
+ j = ++i;
+ for ( ; j < len; j++ ) {
+ if ( Expr.relative[ tokens[ j ].type ] ) {
+ break;
+ }
+ }
+ return setMatcher(
+ i > 1 && elementMatcher( matchers ),
+ i > 1 && toSelector(
+
+ // If the preceding token was a descendant combinator, insert an implicit any-element `*`
+ tokens.slice( 0, i - 1 )
+ .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } )
+ ).replace( rtrimCSS, "$1" ),
+ matcher,
+ i < j && matcherFromTokens( tokens.slice( i, j ) ),
+ j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ),
+ j < len && toSelector( tokens )
+ );
+ }
+ matchers.push( matcher );
+ }
+ }
+
+ return elementMatcher( matchers );
+}
+
+function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
+ var bySet = setMatchers.length > 0,
+ byElement = elementMatchers.length > 0,
+ superMatcher = function( seed, context, xml, results, outermost ) {
+ var elem, j, matcher,
+ matchedCount = 0,
+ i = "0",
+ unmatched = seed && [],
+ setMatched = [],
+ contextBackup = outermostContext,
+
+ // We must always have either seed elements or outermost context
+ elems = seed || byElement && Expr.find.TAG( "*", outermost ),
+
+ // Use integer dirruns iff this is the outermost matcher
+ dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ),
+ len = elems.length;
+
+ if ( outermost ) {
+
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ outermostContext = context == document || context || outermost;
+ }
+
+ // Add elements passing elementMatchers directly to results
+ // Support: iOS <=7 - 9 only
+ // Tolerate NodeList properties (IE: "length"; Safari: <number>) matching
+ // elements by id. (see trac-14142)
+ for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) {
+ if ( byElement && elem ) {
+ j = 0;
+
+ // Support: IE 11+, Edge 17 - 18+
+ // IE/Edge sometimes throw a "Permission denied" error when strict-comparing
+ // two documents; shallow comparisons work.
+ // eslint-disable-next-line eqeqeq
+ if ( !context && elem.ownerDocument != document ) {
+ setDocument( elem );
+ xml = !documentIsHTML;
+ }
+ while ( ( matcher = elementMatchers[ j++ ] ) ) {
+ if ( matcher( elem, context || document, xml ) ) {
+ push.call( results, elem );
+ break;
+ }
+ }
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ }
+ }
+
+ // Track unmatched elements for set filters
+ if ( bySet ) {
+
+ // They will have gone through all possible matchers
+ if ( ( elem = !matcher && elem ) ) {
+ matchedCount--;
+ }
+
+ // Lengthen the array for every element, matched or not
+ if ( seed ) {
+ unmatched.push( elem );
+ }
+ }
+ }
+
+ // `i` is now the count of elements visited above, and adding it to `matchedCount`
+ // makes the latter nonnegative.
+ matchedCount += i;
+
+ // Apply set filters to unmatched elements
+ // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
+ // equals `i`), unless we didn't visit _any_ elements in the above loop because we have
+ // no element matchers and no seed.
+ // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
+ // case, which will result in a "00" `matchedCount` that differs from `i` but is also
+ // numerically zero.
+ if ( bySet && i !== matchedCount ) {
+ j = 0;
+ while ( ( matcher = setMatchers[ j++ ] ) ) {
+ matcher( unmatched, setMatched, context, xml );
+ }
+
+ if ( seed ) {
+
+ // Reintegrate element matches to eliminate the need for sorting
+ if ( matchedCount > 0 ) {
+ while ( i-- ) {
+ if ( !( unmatched[ i ] || setMatched[ i ] ) ) {
+ setMatched[ i ] = pop.call( results );
+ }
+ }
+ }
+
+ // Discard index placeholder values to get only actual matches
+ setMatched = condense( setMatched );
+ }
+
+ // Add matches to results
+ push.apply( results, setMatched );
+
+ // Seedless set matches succeeding multiple successful matchers stipulate sorting
+ if ( outermost && !seed && setMatched.length > 0 &&
+ ( matchedCount + setMatchers.length ) > 1 ) {
+
+ jQuery.uniqueSort( results );
+ }
+ }
+
+ // Override manipulation of globals by nested matchers
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ outermostContext = contextBackup;
+ }
+
+ return unmatched;
+ };
+
+ return bySet ?
+ markFunction( superMatcher ) :
+ superMatcher;
+}
+
+function compile( selector, match /* Internal Use Only */ ) {
+ var i,
+ setMatchers = [],
+ elementMatchers = [],
+ cached = compilerCache[ selector + " " ];
+
+ if ( !cached ) {
+
+ // Generate a function of recursive functions that can be used to check each element
+ if ( !match ) {
+ match = tokenize( selector );
+ }
+ i = match.length;
+ while ( i-- ) {
+ cached = matcherFromTokens( match[ i ] );
+ if ( cached[ expando ] ) {
+ setMatchers.push( cached );
+ } else {
+ elementMatchers.push( cached );
+ }
+ }
+
+ // Cache the compiled function
+ cached = compilerCache( selector,
+ matcherFromGroupMatchers( elementMatchers, setMatchers ) );
+
+ // Save selector and tokenization
+ cached.selector = selector;
+ }
+ return cached;
+}
+
+/**
+ * A low-level selection function that works with jQuery's compiled
+ * selector functions
+ * @param {String|Function} selector A selector or a pre-compiled
+ * selector function built with jQuery selector compile
+ * @param {Element} context
+ * @param {Array} [results]
+ * @param {Array} [seed] A set of elements to match against
+ */
+function select( selector, context, results, seed ) {
+ var i, tokens, token, type, find,
+ compiled = typeof selector === "function" && selector,
+ match = !seed && tokenize( ( selector = compiled.selector || selector ) );
+
+ results = results || [];
+
+ // Try to minimize operations if there is only one selector in the list and no seed
+ // (the latter of which guarantees us context)
+ if ( match.length === 1 ) {
+
+ // Reduce context if the leading compound selector is an ID
+ tokens = match[ 0 ] = match[ 0 ].slice( 0 );
+ if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" &&
+ context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) {
+
+ context = ( Expr.find.ID(
+ token.matches[ 0 ].replace( runescape, funescape ),
+ context
+ ) || [] )[ 0 ];
+ if ( !context ) {
+ return results;
+
+ // Precompiled matchers will still verify ancestry, so step up a level
+ } else if ( compiled ) {
+ context = context.parentNode;
+ }
+
+ selector = selector.slice( tokens.shift().value.length );
+ }
+
+ // Fetch a seed set for right-to-left matching
+ i = matchExpr.needsContext.test( selector ) ? 0 : tokens.length;
+ while ( i-- ) {
+ token = tokens[ i ];
+
+ // Abort if we hit a combinator
+ if ( Expr.relative[ ( type = token.type ) ] ) {
+ break;
+ }
+ if ( ( find = Expr.find[ type ] ) ) {
+
+ // Search, expanding context for leading sibling combinators
+ if ( ( seed = find(
+ token.matches[ 0 ].replace( runescape, funescape ),
+ rsibling.test( tokens[ 0 ].type ) &&
+ testContext( context.parentNode ) || context
+ ) ) ) {
+
+ // If seed is empty or no tokens remain, we can return early
+ tokens.splice( i, 1 );
+ selector = seed.length && toSelector( tokens );
+ if ( !selector ) {
+ push.apply( results, seed );
+ return results;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ // Compile and execute a filtering function if one is not provided
+ // Provide `match` to avoid retokenization if we modified the selector above
+ ( compiled || compile( selector, match ) )(
+ seed,
+ context,
+ !documentIsHTML,
+ results,
+ !context || rsibling.test( selector ) && testContext( context.parentNode ) || context
+ );
+ return results;
+}
+
+// One-time assignments
+
+// Support: Android <=4.0 - 4.1+
+// Sort stability
+support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando;
+
+// Initialize against the default document
+setDocument();
+
+// Support: Android <=4.0 - 4.1+
+// Detached nodes confoundingly follow *each other*
+support.sortDetached = assert( function( el ) {
+
+ // Should return 1, but returns 4 (following)
+ return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1;
+} );
+
+jQuery.find = find;
+
+// Deprecated
+jQuery.expr[ ":" ] = jQuery.expr.pseudos;
+jQuery.unique = jQuery.uniqueSort;
+
+// These have always been private, but they used to be documented as part of
+// Sizzle so let's maintain them for now for backwards compatibility purposes.
+find.compile = compile;
+find.select = select;
+find.setDocument = setDocument;
+find.tokenize = tokenize;
+
+find.escape = jQuery.escapeSelector;
+find.getText = jQuery.text;
+find.isXML = jQuery.isXMLDoc;
+find.selectors = jQuery.expr;
+find.support = jQuery.support;
+find.uniqueSort = jQuery.uniqueSort;
+
+ /* eslint-enable */
+
+} )();
+
+
+var dir = function( elem, dir, until ) {
+ var matched = [],
+ truncate = until !== undefined;
+
+ while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) {
+ if ( elem.nodeType === 1 ) {
+ if ( truncate && jQuery( elem ).is( until ) ) {
+ break;
+ }
+ matched.push( elem );
+ }
+ }
+ return matched;
+};
+
+
+var siblings = function( n, elem ) {
+ var matched = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ matched.push( n );
+ }
+ }
+
+ return matched;
+};
+
+
+var rneedsContext = jQuery.expr.match.needsContext;
+
+var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );
+
+
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, not ) {
+ if ( isFunction( qualifier ) ) {
+ return jQuery.grep( elements, function( elem, i ) {
+ return !!qualifier.call( elem, i, elem ) !== not;
+ } );
+ }
+
+ // Single element
+ if ( qualifier.nodeType ) {
+ return jQuery.grep( elements, function( elem ) {
+ return ( elem === qualifier ) !== not;
+ } );
+ }
+
+ // Arraylike of elements (jQuery, arguments, Array)
+ if ( typeof qualifier !== "string" ) {
+ return jQuery.grep( elements, function( elem ) {
+ return ( indexOf.call( qualifier, elem ) > -1 ) !== not;
+ } );
+ }
+
+ // Filtered directly for both simple and complex selectors
+ return jQuery.filter( qualifier, elements, not );
+}
+
+jQuery.filter = function( expr, elems, not ) {
+ var elem = elems[ 0 ];
+
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ if ( elems.length === 1 && elem.nodeType === 1 ) {
+ return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [];
+ }
+
+ return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
+ return elem.nodeType === 1;
+ } ) );
+};
+
+jQuery.fn.extend( {
+ find: function( selector ) {
+ var i, ret,
+ len = this.length,
+ self = this;
+
+ if ( typeof selector !== "string" ) {
+ return this.pushStack( jQuery( selector ).filter( function() {
+ for ( i = 0; i < len; i++ ) {
+ if ( jQuery.contains( self[ i ], this ) ) {
+ return true;
+ }
+ }
+ } ) );
+ }
+
+ ret = this.pushStack( [] );
+
+ for ( i = 0; i < len; i++ ) {
+ jQuery.find( selector, self[ i ], ret );
+ }
+
+ return len > 1 ? jQuery.uniqueSort( ret ) : ret;
+ },
+ filter: function( selector ) {
+ return this.pushStack( winnow( this, selector || [], false ) );
+ },
+ not: function( selector ) {
+ return this.pushStack( winnow( this, selector || [], true ) );
+ },
+ is: function( selector ) {
+ return !!winnow(
+ this,
+
+ // If this is a positional/relative selector, check membership in the returned set
+ // so $("p:first").is("p:last") won't return true for a doc with two "p".
+ typeof selector === "string" && rneedsContext.test( selector ) ?
+ jQuery( selector ) :
+ selector || [],
+ false
+ ).length;
+ }
+} );
+
+
+// Initialize a jQuery object
+
+
+// A central reference to the root jQuery(document)
+var rootjQuery,
+
+ // A simple way to check for HTML strings
+ // Prioritize #id over <tag> to avoid XSS via location.hash (trac-9521)
+ // Strict HTML recognition (trac-11290: must start with <)
+ // Shortcut simple #id case for speed
+ rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
+
+ init = jQuery.fn.init = function( selector, context, root ) {
+ var match, elem;
+
+ // HANDLE: $(""), $(null), $(undefined), $(false)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Method init() accepts an alternate rootjQuery
+ // so migrate can support jQuery.sub (gh-2101)
+ root = root || rootjQuery;
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ if ( selector[ 0 ] === "<" &&
+ selector[ selector.length - 1 ] === ">" &&
+ selector.length >= 3 ) {
+
+ // Assume that strings that start and end with <> are HTML and skip the regex check
+ match = [ null, selector, null ];
+
+ } else {
+ match = rquickExpr.exec( selector );
+ }
+
+ // Match html or make sure no context is specified for #id
+ if ( match && ( match[ 1 ] || !context ) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[ 1 ] ) {
+ context = context instanceof jQuery ? context[ 0 ] : context;
+
+ // Option to run scripts is true for back-compat
+ // Intentionally let the error be thrown if parseHTML is not present
+ jQuery.merge( this, jQuery.parseHTML(
+ match[ 1 ],
+ context && context.nodeType ? context.ownerDocument || context : document,
+ true
+ ) );
+
+ // HANDLE: $(html, props)
+ if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
+ for ( match in context ) {
+
+ // Properties of context are called as methods if possible
+ if ( isFunction( this[ match ] ) ) {
+ this[ match ]( context[ match ] );
+
+ // ...and otherwise set as attributes
+ } else {
+ this.attr( match, context[ match ] );
+ }
+ }
+ }
+
+ return this;
+
+ // HANDLE: $(#id)
+ } else {
+ elem = document.getElementById( match[ 2 ] );
+
+ if ( elem ) {
+
+ // Inject the element directly into the jQuery object
+ this[ 0 ] = elem;
+ this.length = 1;
+ }
+ return this;
+ }
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return ( context || root ).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
+
+ // HANDLE: $(DOMElement)
+ } else if ( selector.nodeType ) {
+ this[ 0 ] = selector;
+ this.length = 1;
+ return this;
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( isFunction( selector ) ) {
+ return root.ready !== undefined ?
+ root.ready( selector ) :
+
+ // Execute immediately if ready is not present
+ selector( jQuery );
+ }
+
+ return jQuery.makeArray( selector, this );
+ };
+
+// Give the init function the jQuery prototype for later instantiation
+init.prototype = jQuery.fn;
+
+// Initialize central reference
+rootjQuery = jQuery( document );
+
+
+var rparentsprev = /^(?:parents|prev(?:Until|All))/,
+
+ // Methods guaranteed to produce a unique set when starting from a unique set
+ guaranteedUnique = {
+ children: true,
+ contents: true,
+ next: true,
+ prev: true
+ };
+
+jQuery.fn.extend( {
+ has: function( target ) {
+ var targets = jQuery( target, this ),
+ l = targets.length;
+
+ return this.filter( function() {
+ var i = 0;
+ for ( ; i < l; i++ ) {
+ if ( jQuery.contains( this, targets[ i ] ) ) {
+ return true;
+ }
+ }
+ } );
+ },
+
+ closest: function( selectors, context ) {
+ var cur,
+ i = 0,
+ l = this.length,
+ matched = [],
+ targets = typeof selectors !== "string" && jQuery( selectors );
+
+ // Positional selectors never match, since there's no _selection_ context
+ if ( !rneedsContext.test( selectors ) ) {
+ for ( ; i < l; i++ ) {
+ for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {
+
+ // Always skip document fragments
+ if ( cur.nodeType < 11 && ( targets ?
+ targets.index( cur ) > -1 :
+
+ // Don't pass non-elements to jQuery#find
+ cur.nodeType === 1 &&
+ jQuery.find.matchesSelector( cur, selectors ) ) ) {
+
+ matched.push( cur );
+ break;
+ }
+ }
+ }
+ }
+
+ return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched );
+ },
+
+ // Determine the position of an element within the set
+ index: function( elem ) {
+
+ // No argument, return index in parent
+ if ( !elem ) {
+ return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
+ }
+
+ // Index in selector
+ if ( typeof elem === "string" ) {
+ return indexOf.call( jQuery( elem ), this[ 0 ] );
+ }
+
+ // Locate the position of the desired element
+ return indexOf.call( this,
+
+ // If it receives a jQuery object, the first element is used
+ elem.jquery ? elem[ 0 ] : elem
+ );
+ },
+
+ add: function( selector, context ) {
+ return this.pushStack(
+ jQuery.uniqueSort(
+ jQuery.merge( this.get(), jQuery( selector, context ) )
+ )
+ );
+ },
+
+ addBack: function( selector ) {
+ return this.add( selector == null ?
+ this.prevObject : this.prevObject.filter( selector )
+ );
+ }
+} );
+
+function sibling( cur, dir ) {
+ while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {}
+ return cur;
+}
+
+jQuery.each( {
+ parent: function( elem ) {
+ var parent = elem.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+ parents: function( elem ) {
+ return dir( elem, "parentNode" );
+ },
+ parentsUntil: function( elem, _i, until ) {
+ return dir( elem, "parentNode", until );
+ },
+ next: function( elem ) {
+ return sibling( elem, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return sibling( elem, "previousSibling" );
+ },
+ nextAll: function( elem ) {
+ return dir( elem, "nextSibling" );
+ },
+ prevAll: function( elem ) {
+ return dir( elem, "previousSibling" );
+ },
+ nextUntil: function( elem, _i, until ) {
+ return dir( elem, "nextSibling", until );
+ },
+ prevUntil: function( elem, _i, until ) {
+ return dir( elem, "previousSibling", until );
+ },
+ siblings: function( elem ) {
+ return siblings( ( elem.parentNode || {} ).firstChild, elem );
+ },
+ children: function( elem ) {
+ return siblings( elem.firstChild );
+ },
+ contents: function( elem ) {
+ if ( elem.contentDocument != null &&
+
+ // Support: IE 11+
+ // <object> elements with no `data` attribute has an object
+ // `contentDocument` with a `null` prototype.
+ getProto( elem.contentDocument ) ) {
+
+ return elem.contentDocument;
+ }
+
+ // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only
+ // Treat the template element as a regular one in browsers that
+ // don't support it.
+ if ( nodeName( elem, "template" ) ) {
+ elem = elem.content || elem;
+ }
+
+ return jQuery.merge( [], elem.childNodes );
+ }
+}, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var matched = jQuery.map( this, fn, until );
+
+ if ( name.slice( -5 ) !== "Until" ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ matched = jQuery.filter( selector, matched );
+ }
+
+ if ( this.length > 1 ) {
+
+ // Remove duplicates
+ if ( !guaranteedUnique[ name ] ) {
+ jQuery.uniqueSort( matched );
+ }
+
+ // Reverse order for parents* and prev-derivatives
+ if ( rparentsprev.test( name ) ) {
+ matched.reverse();
+ }
+ }
+
+ return this.pushStack( matched );
+ };
+} );
+var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g );
+
+
+
+// Convert String-formatted options into Object-formatted ones
+function createOptions( options ) {
+ var object = {};
+ jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) {
+ object[ flag ] = true;
+ } );
+ return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ * options: an optional list of space-separated options that will change how
+ * the callback list behaves or a more traditional option object
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible options:
+ *
+ * once: will ensure the callback list can only be fired once (like a Deferred)
+ *
+ * memory: will keep track of previous values and will call any callback added
+ * after the list has been fired right away with the latest "memorized"
+ * values (like a Deferred)
+ *
+ * unique: will ensure a callback can only be added once (no duplicate in the list)
+ *
+ * stopOnFalse: interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( options ) {
+
+ // Convert options from String-formatted to Object-formatted if needed
+ // (we check in cache first)
+ options = typeof options === "string" ?
+ createOptions( options ) :
+ jQuery.extend( {}, options );
+
+ var // Flag to know if list is currently firing
+ firing,
+
+ // Last fire value for non-forgettable lists
+ memory,
+
+ // Flag to know if list was already fired
+ fired,
+
+ // Flag to prevent firing
+ locked,
+
+ // Actual callback list
+ list = [],
+
+ // Queue of execution data for repeatable lists
+ queue = [],
+
+ // Index of currently firing callback (modified by add/remove as needed)
+ firingIndex = -1,
+
+ // Fire callbacks
+ fire = function() {
+
+ // Enforce single-firing
+ locked = locked || options.once;
+
+ // Execute callbacks for all pending executions,
+ // respecting firingIndex overrides and runtime changes
+ fired = firing = true;
+ for ( ; queue.length; firingIndex = -1 ) {
+ memory = queue.shift();
+ while ( ++firingIndex < list.length ) {
+
+ // Run callback and check for early termination
+ if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
+ options.stopOnFalse ) {
+
+ // Jump to end and forget the data so .add doesn't re-fire
+ firingIndex = list.length;
+ memory = false;
+ }
+ }
+ }
+
+ // Forget the data if we're done with it
+ if ( !options.memory ) {
+ memory = false;
+ }
+
+ firing = false;
+
+ // Clean up if we're done firing for good
+ if ( locked ) {
+
+ // Keep an empty list if we have data for future add calls
+ if ( memory ) {
+ list = [];
+
+ // Otherwise, this object is spent
+ } else {
+ list = "";
+ }
+ }
+ },
+
+ // Actual Callbacks object
+ self = {
+
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+
+ // If we have memory from a past run, we should fire after adding
+ if ( memory && !firing ) {
+ firingIndex = list.length - 1;
+ queue.push( memory );
+ }
+
+ ( function add( args ) {
+ jQuery.each( args, function( _, arg ) {
+ if ( isFunction( arg ) ) {
+ if ( !options.unique || !self.has( arg ) ) {
+ list.push( arg );
+ }
+ } else if ( arg && arg.length && toType( arg ) !== "string" ) {
+
+ // Inspect recursively
+ add( arg );
+ }
+ } );
+ } )( arguments );
+
+ if ( memory && !firing ) {
+ fire();
+ }
+ }
+ return this;
+ },
+
+ // Remove a callback from the list
+ remove: function() {
+ jQuery.each( arguments, function( _, arg ) {
+ var index;
+ while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+ list.splice( index, 1 );
+
+ // Handle firing indexes
+ if ( index <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ } );
+ return this;
+ },
+
+ // Check if a given callback is in the list.
+ // If no argument is given, return whether or not list has callbacks attached.
+ has: function( fn ) {
+ return fn ?
+ jQuery.inArray( fn, list ) > -1 :
+ list.length > 0;
+ },
+
+ // Remove all callbacks from the list
+ empty: function() {
+ if ( list ) {
+ list = [];
+ }
+ return this;
+ },
+
+ // Disable .fire and .add
+ // Abort any current/pending executions
+ // Clear all callbacks and values
+ disable: function() {
+ locked = queue = [];
+ list = memory = "";
+ return this;
+ },
+ disabled: function() {
+ return !list;
+ },
+
+ // Disable .fire
+ // Also disable .add unless we have memory (since it would have no effect)
+ // Abort any pending executions
+ lock: function() {
+ locked = queue = [];
+ if ( !memory && !firing ) {
+ list = memory = "";
+ }
+ return this;
+ },
+ locked: function() {
+ return !!locked;
+ },
+
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ if ( !locked ) {
+ args = args || [];
+ args = [ context, args.slice ? args.slice() : args ];
+ queue.push( args );
+ if ( !firing ) {
+ fire();
+ }
+ }
+ return this;
+ },
+
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!fired;
+ }
+ };
+
+ return self;
+};
+
+
+function Identity( v ) {
+ return v;
+}
+function Thrower( ex ) {
+ throw ex;
+}
+
+function adoptValue( value, resolve, reject, noValue ) {
+ var method;
+
+ try {
+
+ // Check for promise aspect first to privilege synchronous behavior
+ if ( value && isFunction( ( method = value.promise ) ) ) {
+ method.call( value ).done( resolve ).fail( reject );
+
+ // Other thenables
+ } else if ( value && isFunction( ( method = value.then ) ) ) {
+ method.call( value, resolve, reject );
+
+ // Other non-thenables
+ } else {
+
+ // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer:
+ // * false: [ value ].slice( 0 ) => resolve( value )
+ // * true: [ value ].slice( 1 ) => resolve()
+ resolve.apply( undefined, [ value ].slice( noValue ) );
+ }
+
+ // For Promises/A+, convert exceptions into rejections
+ // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in
+ // Deferred#then to conditionally suppress rejection.
+ } catch ( value ) {
+
+ // Support: Android 4.0 only
+ // Strict mode functions invoked without .call/.apply get global-object context
+ reject.apply( undefined, [ value ] );
+ }
+}
+
+jQuery.extend( {
+
+ Deferred: function( func ) {
+ var tuples = [
+
+ // action, add listener, callbacks,
+ // ... .then handlers, argument index, [final state]
+ [ "notify", "progress", jQuery.Callbacks( "memory" ),
+ jQuery.Callbacks( "memory" ), 2 ],
+ [ "resolve", "done", jQuery.Callbacks( "once memory" ),
+ jQuery.Callbacks( "once memory" ), 0, "resolved" ],
+ [ "reject", "fail", jQuery.Callbacks( "once memory" ),
+ jQuery.Callbacks( "once memory" ), 1, "rejected" ]
+ ],
+ state = "pending",
+ promise = {
+ state: function() {
+ return state;
+ },
+ always: function() {
+ deferred.done( arguments ).fail( arguments );
+ return this;
+ },
+ "catch": function( fn ) {
+ return promise.then( null, fn );
+ },
+
+ // Keep pipe for back-compat
+ pipe: function( /* fnDone, fnFail, fnProgress */ ) {
+ var fns = arguments;
+
+ return jQuery.Deferred( function( newDefer ) {
+ jQuery.each( tuples, function( _i, tuple ) {
+
+ // Map tuples (progress, done, fail) to arguments (done, fail, progress)
+ var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ];
+
+ // deferred.progress(function() { bind to newDefer or newDefer.notify })
+ // deferred.done(function() { bind to newDefer or newDefer.resolve })
+ // deferred.fail(function() { bind to newDefer or newDefer.reject })
+ deferred[ tuple[ 1 ] ]( function() {
+ var returned = fn && fn.apply( this, arguments );
+ if ( returned && isFunction( returned.promise ) ) {
+ returned.promise()
+ .progress( newDefer.notify )
+ .done( newDefer.resolve )
+ .fail( newDefer.reject );
+ } else {
+ newDefer[ tuple[ 0 ] + "With" ](
+ this,
+ fn ? [ returned ] : arguments
+ );
+ }
+ } );
+ } );
+ fns = null;
+ } ).promise();
+ },
+ then: function( onFulfilled, onRejected, onProgress ) {
+ var maxDepth = 0;
+ function resolve( depth, deferred, handler, special ) {
+ return function() {
+ var that = this,
+ args = arguments,
+ mightThrow = function() {
+ var returned, then;
+
+ // Support: Promises/A+ section 2.3.3.3.3
+ // https://promisesaplus.com/#point-59
+ // Ignore double-resolution attempts
+ if ( depth < maxDepth ) {
+ return;
+ }
+
+ returned = handler.apply( that, args );
+
+ // Support: Promises/A+ section 2.3.1
+ // https://promisesaplus.com/#point-48
+ if ( returned === deferred.promise() ) {
+ throw new TypeError( "Thenable self-resolution" );
+ }
+
+ // Support: Promises/A+ sections 2.3.3.1, 3.5
+ // https://promisesaplus.com/#point-54
+ // https://promisesaplus.com/#point-75
+ // Retrieve `then` only once
+ then = returned &&
+
+ // Support: Promises/A+ section 2.3.4
+ // https://promisesaplus.com/#point-64
+ // Only check objects and functions for thenability
+ ( typeof returned === "object" ||
+ typeof returned === "function" ) &&
+ returned.then;
+
+ // Handle a returned thenable
+ if ( isFunction( then ) ) {
+
+ // Special processors (notify) just wait for resolution
+ if ( special ) {
+ then.call(
+ returned,
+ resolve( maxDepth, deferred, Identity, special ),
+ resolve( maxDepth, deferred, Thrower, special )
+ );
+
+ // Normal processors (resolve) also hook into progress
+ } else {
+
+ // ...and disregard older resolution values
+ maxDepth++;
+
+ then.call(
+ returned,
+ resolve( maxDepth, deferred, Identity, special ),
+ resolve( maxDepth, deferred, Thrower, special ),
+ resolve( maxDepth, deferred, Identity,
+ deferred.notifyWith )
+ );
+ }
+
+ // Handle all other returned values
+ } else {
+
+ // Only substitute handlers pass on context
+ // and multiple values (non-spec behavior)
+ if ( handler !== Identity ) {
+ that = undefined;
+ args = [ returned ];
+ }
+
+ // Process the value(s)
+ // Default process is resolve
+ ( special || deferred.resolveWith )( that, args );
+ }
+ },
+
+ // Only normal processors (resolve) catch and reject exceptions
+ process = special ?
+ mightThrow :
+ function() {
+ try {
+ mightThrow();
+ } catch ( e ) {
+
+ if ( jQuery.Deferred.exceptionHook ) {
+ jQuery.Deferred.exceptionHook( e,
+ process.error );
+ }
+
+ // Support: Promises/A+ section 2.3.3.3.4.1
+ // https://promisesaplus.com/#point-61
+ // Ignore post-resolution exceptions
+ if ( depth + 1 >= maxDepth ) {
+
+ // Only substitute handlers pass on context
+ // and multiple values (non-spec behavior)
+ if ( handler !== Thrower ) {
+ that = undefined;
+ args = [ e ];
+ }
+
+ deferred.rejectWith( that, args );
+ }
+ }
+ };
+
+ // Support: Promises/A+ section 2.3.3.3.1
+ // https://promisesaplus.com/#point-57
+ // Re-resolve promises immediately to dodge false rejection from
+ // subsequent errors
+ if ( depth ) {
+ process();
+ } else {
+
+ // Call an optional hook to record the error, in case of exception
+ // since it's otherwise lost when execution goes async
+ if ( jQuery.Deferred.getErrorHook ) {
+ process.error = jQuery.Deferred.getErrorHook();
+
+ // The deprecated alias of the above. While the name suggests
+ // returning the stack, not an error instance, jQuery just passes
+ // it directly to `console.warn` so both will work; an instance
+ // just better cooperates with source maps.
+ } else if ( jQuery.Deferred.getStackHook ) {
+ process.error = jQuery.Deferred.getStackHook();
+ }
+ window.setTimeout( process );
+ }
+ };
+ }
+
+ return jQuery.Deferred( function( newDefer ) {
+
+ // progress_handlers.add( ... )
+ tuples[ 0 ][ 3 ].add(
+ resolve(
+ 0,
+ newDefer,
+ isFunction( onProgress ) ?
+ onProgress :
+ Identity,
+ newDefer.notifyWith
+ )
+ );
+
+ // fulfilled_handlers.add( ... )
+ tuples[ 1 ][ 3 ].add(
+ resolve(
+ 0,
+ newDefer,
+ isFunction( onFulfilled ) ?
+ onFulfilled :
+ Identity
+ )
+ );
+
+ // rejected_handlers.add( ... )
+ tuples[ 2 ][ 3 ].add(
+ resolve(
+ 0,
+ newDefer,
+ isFunction( onRejected ) ?
+ onRejected :
+ Thrower
+ )
+ );
+ } ).promise();
+ },
+
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ return obj != null ? jQuery.extend( obj, promise ) : promise;
+ }
+ },
+ deferred = {};
+
+ // Add list-specific methods
+ jQuery.each( tuples, function( i, tuple ) {
+ var list = tuple[ 2 ],
+ stateString = tuple[ 5 ];
+
+ // promise.progress = list.add
+ // promise.done = list.add
+ // promise.fail = list.add
+ promise[ tuple[ 1 ] ] = list.add;
+
+ // Handle state
+ if ( stateString ) {
+ list.add(
+ function() {
+
+ // state = "resolved" (i.e., fulfilled)
+ // state = "rejected"
+ state = stateString;
+ },
+
+ // rejected_callbacks.disable
+ // fulfilled_callbacks.disable
+ tuples[ 3 - i ][ 2 ].disable,
+
+ // rejected_handlers.disable
+ // fulfilled_handlers.disable
+ tuples[ 3 - i ][ 3 ].disable,
+
+ // progress_callbacks.lock
+ tuples[ 0 ][ 2 ].lock,
+
+ // progress_handlers.lock
+ tuples[ 0 ][ 3 ].lock
+ );
+ }
+
+ // progress_handlers.fire
+ // fulfilled_handlers.fire
+ // rejected_handlers.fire
+ list.add( tuple[ 3 ].fire );
+
+ // deferred.notify = function() { deferred.notifyWith(...) }
+ // deferred.resolve = function() { deferred.resolveWith(...) }
+ // deferred.reject = function() { deferred.rejectWith(...) }
+ deferred[ tuple[ 0 ] ] = function() {
+ deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments );
+ return this;
+ };
+
+ // deferred.notifyWith = list.fireWith
+ // deferred.resolveWith = list.fireWith
+ // deferred.rejectWith = list.fireWith
+ deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
+ } );
+
+ // Make the deferred a promise
+ promise.promise( deferred );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+
+ // All done!
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( singleValue ) {
+ var
+
+ // count of uncompleted subordinates
+ remaining = arguments.length,
+
+ // count of unprocessed arguments
+ i = remaining,
+
+ // subordinate fulfillment data
+ resolveContexts = Array( i ),
+ resolveValues = slice.call( arguments ),
+
+ // the primary Deferred
+ primary = jQuery.Deferred(),
+
+ // subordinate callback factory
+ updateFunc = function( i ) {
+ return function( value ) {
+ resolveContexts[ i ] = this;
+ resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
+ if ( !( --remaining ) ) {
+ primary.resolveWith( resolveContexts, resolveValues );
+ }
+ };
+ };
+
+ // Single- and empty arguments are adopted like Promise.resolve
+ if ( remaining <= 1 ) {
+ adoptValue( singleValue, primary.done( updateFunc( i ) ).resolve, primary.reject,
+ !remaining );
+
+ // Use .then() to unwrap secondary thenables (cf. gh-3000)
+ if ( primary.state() === "pending" ||
+ isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {
+
+ return primary.then();
+ }
+ }
+
+ // Multiple arguments are aggregated like Promise.all array elements
+ while ( i-- ) {
+ adoptValue( resolveValues[ i ], updateFunc( i ), primary.reject );
+ }
+
+ return primary.promise();
+ }
+} );
+
+
+// These usually indicate a programmer mistake during development,
+// warn about them ASAP rather than swallowing them by default.
+var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;
+
+// If `jQuery.Deferred.getErrorHook` is defined, `asyncError` is an error
+// captured before the async barrier to get the original error cause
+// which may otherwise be hidden.
+jQuery.Deferred.exceptionHook = function( error, asyncError ) {
+
+ // Support: IE 8 - 9 only
+ // Console exists when dev tools are open, which can happen at any time
+ if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) {
+ window.console.warn( "jQuery.Deferred exception: " + error.message,
+ error.stack, asyncError );
+ }
+};
+
+
+
+
+jQuery.readyException = function( error ) {
+ window.setTimeout( function() {
+ throw error;
+ } );
+};
+
+
+
+
+// The deferred used on DOM ready
+var readyList = jQuery.Deferred();
+
+jQuery.fn.ready = function( fn ) {
+
+ readyList
+ .then( fn )
+
+ // Wrap jQuery.readyException in a function so that the lookup
+ // happens at the time of error handling instead of callback
+ // registration.
+ .catch( function( error ) {
+ jQuery.readyException( error );
+ } );
+
+ return this;
+};
+
+jQuery.extend( {
+
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // A counter to track how many items to wait for before
+ // the ready event fires. See trac-6781
+ readyWait: 1,
+
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+
+ // Abort if there are pending holds or we're already ready
+ if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+ return;
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
+
+ // If there are functions bound, to execute
+ readyList.resolveWith( document, [ jQuery ] );
+ }
+} );
+
+jQuery.ready.then = readyList.then;
+
+// The ready event handler and self cleanup method
+function completed() {
+ document.removeEventListener( "DOMContentLoaded", completed );
+ window.removeEventListener( "load", completed );
+ jQuery.ready();
+}
+
+// Catch cases where $(document).ready() is called
+// after the browser event has already occurred.
+// Support: IE <=9 - 10 only
+// Older IE sometimes signals "interactive" too soon
+if ( document.readyState === "complete" ||
+ ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {
+
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ window.setTimeout( jQuery.ready );
+
+} else {
+
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", completed );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", completed );
+}
+
+
+
+
+// Multifunctional method to get and set values of a collection
+// The value/s can optionally be executed if it's a function
+var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
+ var i = 0,
+ len = elems.length,
+ bulk = key == null;
+
+ // Sets many values
+ if ( toType( key ) === "object" ) {
+ chainable = true;
+ for ( i in key ) {
+ access( elems, fn, i, key[ i ], true, emptyGet, raw );
+ }
+
+ // Sets one value
+ } else if ( value !== undefined ) {
+ chainable = true;
+
+ if ( !isFunction( value ) ) {
+ raw = true;
+ }
+
+ if ( bulk ) {
+
+ // Bulk operations run against the entire set
+ if ( raw ) {
+ fn.call( elems, value );
+ fn = null;
+
+ // ...except when executing function values
+ } else {
+ bulk = fn;
+ fn = function( elem, _key, value ) {
+ return bulk.call( jQuery( elem ), value );
+ };
+ }
+ }
+
+ if ( fn ) {
+ for ( ; i < len; i++ ) {
+ fn(
+ elems[ i ], key, raw ?
+ value :
+ value.call( elems[ i ], i, fn( elems[ i ], key ) )
+ );
+ }
+ }
+ }
+
+ if ( chainable ) {
+ return elems;
+ }
+
+ // Gets
+ if ( bulk ) {
+ return fn.call( elems );
+ }
+
+ return len ? fn( elems[ 0 ], key ) : emptyGet;
+};
+
+
+// Matches dashed string for camelizing
+var rmsPrefix = /^-ms-/,
+ rdashAlpha = /-([a-z])/g;
+
+// Used by camelCase as callback to replace()
+function fcamelCase( _all, letter ) {
+ return letter.toUpperCase();
+}
+
+// Convert dashed to camelCase; used by the css and data modules
+// Support: IE <=9 - 11, Edge 12 - 15
+// Microsoft forgot to hump their vendor prefix (trac-9572)
+function camelCase( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+}
+var acceptData = function( owner ) {
+
+ // Accepts only:
+ // - Node
+ // - Node.ELEMENT_NODE
+ // - Node.DOCUMENT_NODE
+ // - Object
+ // - Any
+ return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
+};
+
+
+
+
+function Data() {
+ this.expando = jQuery.expando + Data.uid++;
+}
+
+Data.uid = 1;
+
+Data.prototype = {
+
+ cache: function( owner ) {
+
+ // Check if the owner object already has a cache
+ var value = owner[ this.expando ];
+
+ // If not, create one
+ if ( !value ) {
+ value = {};
+
+ // We can accept data for non-element nodes in modern browsers,
+ // but we should not, see trac-8335.
+ // Always return an empty object.
+ if ( acceptData( owner ) ) {
+
+ // If it is a node unlikely to be stringify-ed or looped over
+ // use plain assignment
+ if ( owner.nodeType ) {
+ owner[ this.expando ] = value;
+
+ // Otherwise secure it in a non-enumerable property
+ // configurable must be true to allow the property to be
+ // deleted when data is removed
+ } else {
+ Object.defineProperty( owner, this.expando, {
+ value: value,
+ configurable: true
+ } );
+ }
+ }
+ }
+
+ return value;
+ },
+ set: function( owner, data, value ) {
+ var prop,
+ cache = this.cache( owner );
+
+ // Handle: [ owner, key, value ] args
+ // Always use camelCase key (gh-2257)
+ if ( typeof data === "string" ) {
+ cache[ camelCase( data ) ] = value;
+
+ // Handle: [ owner, { properties } ] args
+ } else {
+
+ // Copy the properties one-by-one to the cache object
+ for ( prop in data ) {
+ cache[ camelCase( prop ) ] = data[ prop ];
+ }
+ }
+ return cache;
+ },
+ get: function( owner, key ) {
+ return key === undefined ?
+ this.cache( owner ) :
+
+ // Always use camelCase key (gh-2257)
+ owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ];
+ },
+ access: function( owner, key, value ) {
+
+ // In cases where either:
+ //
+ // 1. No key was specified
+ // 2. A string key was specified, but no value provided
+ //
+ // Take the "read" path and allow the get method to determine
+ // which value to return, respectively either:
+ //
+ // 1. The entire cache object
+ // 2. The data stored at the key
+ //
+ if ( key === undefined ||
+ ( ( key && typeof key === "string" ) && value === undefined ) ) {
+
+ return this.get( owner, key );
+ }
+
+ // When the key is not a string, or both a key and value
+ // are specified, set or extend (existing objects) with either:
+ //
+ // 1. An object of properties
+ // 2. A key and value
+ //
+ this.set( owner, key, value );
+
+ // Since the "set" path can have two possible entry points
+ // return the expected data based on which path was taken[*]
+ return value !== undefined ? value : key;
+ },
+ remove: function( owner, key ) {
+ var i,
+ cache = owner[ this.expando ];
+
+ if ( cache === undefined ) {
+ return;
+ }
+
+ if ( key !== undefined ) {
+
+ // Support array or space separated string of keys
+ if ( Array.isArray( key ) ) {
+
+ // If key is an array of keys...
+ // We always set camelCase keys, so remove that.
+ key = key.map( camelCase );
+ } else {
+ key = camelCase( key );
+
+ // If a key with the spaces exists, use it.
+ // Otherwise, create an array by matching non-whitespace
+ key = key in cache ?
+ [ key ] :
+ ( key.match( rnothtmlwhite ) || [] );
+ }
+
+ i = key.length;
+
+ while ( i-- ) {
+ delete cache[ key[ i ] ];
+ }
+ }
+
+ // Remove the expando if there's no more data
+ if ( key === undefined || jQuery.isEmptyObject( cache ) ) {
+
+ // Support: Chrome <=35 - 45
+ // Webkit & Blink performance suffers when deleting properties
+ // from DOM nodes, so set to undefined instead
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)
+ if ( owner.nodeType ) {
+ owner[ this.expando ] = undefined;
+ } else {
+ delete owner[ this.expando ];
+ }
+ }
+ },
+ hasData: function( owner ) {
+ var cache = owner[ this.expando ];
+ return cache !== undefined && !jQuery.isEmptyObject( cache );
+ }
+};
+var dataPriv = new Data();
+
+var dataUser = new Data();
+
+
+
+// Implementation Summary
+//
+// 1. Enforce API surface and semantic compatibility with 1.9.x branch
+// 2. Improve the module's maintainability by reducing the storage
+// paths to a single mechanism.
+// 3. Use the same single mechanism to support "private" and "user" data.
+// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
+// 5. Avoid exposing implementation details on user objects (eg. expando properties)
+// 6. Provide a clear path for implementation upgrade to WeakMap in 2014
+
+var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
+ rmultiDash = /[A-Z]/g;
+
+function getData( data ) {
+ if ( data === "true" ) {
+ return true;
+ }
+
+ if ( data === "false" ) {
+ return false;
+ }
+
+ if ( data === "null" ) {
+ return null;
+ }
+
+ // Only convert to a number if it doesn't change the string
+ if ( data === +data + "" ) {
+ return +data;
+ }
+
+ if ( rbrace.test( data ) ) {
+ return JSON.parse( data );
+ }
+
+ return data;
+}
+
+function dataAttr( elem, key, data ) {
+ var name;
+
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+ name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase();
+ data = elem.getAttribute( name );
+
+ if ( typeof data === "string" ) {
+ try {
+ data = getData( data );
+ } catch ( e ) {}
+
+ // Make sure we set the data so it isn't changed later
+ dataUser.set( elem, key, data );
+ } else {
+ data = undefined;
+ }
+ }
+ return data;
+}
+
+jQuery.extend( {
+ hasData: function( elem ) {
+ return dataUser.hasData( elem ) || dataPriv.hasData( elem );
+ },
+
+ data: function( elem, name, data ) {
+ return dataUser.access( elem, name, data );
+ },
+
+ removeData: function( elem, name ) {
+ dataUser.remove( elem, name );
+ },
+
+ // TODO: Now that all calls to _data and _removeData have been replaced
+ // with direct calls to dataPriv methods, these can be deprecated.
+ _data: function( elem, name, data ) {
+ return dataPriv.access( elem, name, data );
+ },
+
+ _removeData: function( elem, name ) {
+ dataPriv.remove( elem, name );
+ }
+} );
+
+jQuery.fn.extend( {
+ data: function( key, value ) {
+ var i, name, data,
+ elem = this[ 0 ],
+ attrs = elem && elem.attributes;
+
+ // Gets all values
+ if ( key === undefined ) {
+ if ( this.length ) {
+ data = dataUser.get( elem );
+
+ if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
+ i = attrs.length;
+ while ( i-- ) {
+
+ // Support: IE 11 only
+ // The attrs elements can be null (trac-14894)
+ if ( attrs[ i ] ) {
+ name = attrs[ i ].name;
+ if ( name.indexOf( "data-" ) === 0 ) {
+ name = camelCase( name.slice( 5 ) );
+ dataAttr( elem, name, data[ name ] );
+ }
+ }
+ }
+ dataPriv.set( elem, "hasDataAttrs", true );
+ }
+ }
+
+ return data;
+ }
+
+ // Sets multiple values
+ if ( typeof key === "object" ) {
+ return this.each( function() {
+ dataUser.set( this, key );
+ } );
+ }
+
+ return access( this, function( value ) {
+ var data;
+
+ // The calling jQuery object (element matches) is not empty
+ // (and therefore has an element appears at this[ 0 ]) and the
+ // `value` parameter was not undefined. An empty jQuery object
+ // will result in `undefined` for elem = this[ 0 ] which will
+ // throw an exception if an attempt to read a data cache is made.
+ if ( elem && value === undefined ) {
+
+ // Attempt to get data from the cache
+ // The key will always be camelCased in Data
+ data = dataUser.get( elem, key );
+ if ( data !== undefined ) {
+ return data;
+ }
+
+ // Attempt to "discover" the data in
+ // HTML5 custom data-* attrs
+ data = dataAttr( elem, key );
+ if ( data !== undefined ) {
+ return data;
+ }
+
+ // We tried really hard, but the data doesn't exist.
+ return;
+ }
+
+ // Set the data...
+ this.each( function() {
+
+ // We always store the camelCased key
+ dataUser.set( this, key, value );
+ } );
+ }, null, value, arguments.length > 1, null, true );
+ },
+
+ removeData: function( key ) {
+ return this.each( function() {
+ dataUser.remove( this, key );
+ } );
+ }
+} );
+
+
+jQuery.extend( {
+ queue: function( elem, type, data ) {
+ var queue;
+
+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ queue = dataPriv.get( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !queue || Array.isArray( data ) ) {
+ queue = dataPriv.access( elem, type, jQuery.makeArray( data ) );
+ } else {
+ queue.push( data );
+ }
+ }
+ return queue || [];
+ }
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ startLength = queue.length,
+ fn = queue.shift(),
+ hooks = jQuery._queueHooks( elem, type ),
+ next = function() {
+ jQuery.dequeue( elem, type );
+ };
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ startLength--;
+ }
+
+ if ( fn ) {
+
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
+ }
+
+ // Clear up the last queue stop function
+ delete hooks.stop;
+ fn.call( elem, next, hooks );
+ }
+
+ if ( !startLength && hooks ) {
+ hooks.empty.fire();
+ }
+ },
+
+ // Not public - generate a queueHooks object, or return the current one
+ _queueHooks: function( elem, type ) {
+ var key = type + "queueHooks";
+ return dataPriv.get( elem, key ) || dataPriv.access( elem, key, {
+ empty: jQuery.Callbacks( "once memory" ).add( function() {
+ dataPriv.remove( elem, [ type + "queue", key ] );
+ } )
+ } );
+ }
+} );
+
+jQuery.fn.extend( {
+ queue: function( type, data ) {
+ var setter = 2;
+
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ setter--;
+ }
+
+ if ( arguments.length < setter ) {
+ return jQuery.queue( this[ 0 ], type );
+ }
+
+ return data === undefined ?
+ this :
+ this.each( function() {
+ var queue = jQuery.queue( this, type, data );
+
+ // Ensure a hooks for this queue
+ jQuery._queueHooks( this, type );
+
+ if ( type === "fx" && queue[ 0 ] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ } );
+ },
+ dequeue: function( type ) {
+ return this.each( function() {
+ jQuery.dequeue( this, type );
+ } );
+ },
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ },
+
+ // Get a promise resolved when queues of a certain type
+ // are emptied (fx is the type by default)
+ promise: function( type, obj ) {
+ var tmp,
+ count = 1,
+ defer = jQuery.Deferred(),
+ elements = this,
+ i = this.length,
+ resolve = function() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ };
+
+ if ( typeof type !== "string" ) {
+ obj = type;
+ type = undefined;
+ }
+ type = type || "fx";
+
+ while ( i-- ) {
+ tmp = dataPriv.get( elements[ i ], type + "queueHooks" );
+ if ( tmp && tmp.empty ) {
+ count++;
+ tmp.empty.add( resolve );
+ }
+ }
+ resolve();
+ return defer.promise( obj );
+ }
+} );
+var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;
+
+var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" );
+
+
+var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
+
+var documentElement = document.documentElement;
+
+
+
+ var isAttached = function( elem ) {
+ return jQuery.contains( elem.ownerDocument, elem );
+ },
+ composed = { composed: true };
+
+ // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only
+ // Check attachment across shadow DOM boundaries when possible (gh-3504)
+ // Support: iOS 10.0-10.2 only
+ // Early iOS 10 versions support `attachShadow` but not `getRootNode`,
+ // leading to errors. We need to check for `getRootNode`.
+ if ( documentElement.getRootNode ) {
+ isAttached = function( elem ) {
+ return jQuery.contains( elem.ownerDocument, elem ) ||
+ elem.getRootNode( composed ) === elem.ownerDocument;
+ };
+ }
+var isHiddenWithinTree = function( elem, el ) {
+
+ // isHiddenWithinTree might be called from jQuery#filter function;
+ // in that case, element will be second argument
+ elem = el || elem;
+
+ // Inline style trumps all
+ return elem.style.display === "none" ||
+ elem.style.display === "" &&
+
+ // Otherwise, check computed style
+ // Support: Firefox <=43 - 45
+ // Disconnected elements can have computed display: none, so first confirm that elem is
+ // in the document.
+ isAttached( elem ) &&
+
+ jQuery.css( elem, "display" ) === "none";
+ };
+
+
+
+function adjustCSS( elem, prop, valueParts, tween ) {
+ var adjusted, scale,
+ maxIterations = 20,
+ currentValue = tween ?
+ function() {
+ return tween.cur();
+ } :
+ function() {
+ return jQuery.css( elem, prop, "" );
+ },
+ initial = currentValue(),
+ unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
+
+ // Starting value computation is required for potential unit mismatches
+ initialInUnit = elem.nodeType &&
+ ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) &&
+ rcssNum.exec( jQuery.css( elem, prop ) );
+
+ if ( initialInUnit && initialInUnit[ 3 ] !== unit ) {
+
+ // Support: Firefox <=54
+ // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144)
+ initial = initial / 2;
+
+ // Trust units reported by jQuery.css
+ unit = unit || initialInUnit[ 3 ];
+
+ // Iteratively approximate from a nonzero starting point
+ initialInUnit = +initial || 1;
+
+ while ( maxIterations-- ) {
+
+ // Evaluate and update our best guess (doubling guesses that zero out).
+ // Finish if the scale equals or crosses 1 (making the old*new product non-positive).
+ jQuery.style( elem, prop, initialInUnit + unit );
+ if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) {
+ maxIterations = 0;
+ }
+ initialInUnit = initialInUnit / scale;
+
+ }
+
+ initialInUnit = initialInUnit * 2;
+ jQuery.style( elem, prop, initialInUnit + unit );
+
+ // Make sure we update the tween properties later on
+ valueParts = valueParts || [];
+ }
+
+ if ( valueParts ) {
+ initialInUnit = +initialInUnit || +initial || 0;
+
+ // Apply relative offset (+=/-=) if specified
+ adjusted = valueParts[ 1 ] ?
+ initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :
+ +valueParts[ 2 ];
+ if ( tween ) {
+ tween.unit = unit;
+ tween.start = initialInUnit;
+ tween.end = adjusted;
+ }
+ }
+ return adjusted;
+}
+
+
+var defaultDisplayMap = {};
+
+function getDefaultDisplay( elem ) {
+ var temp,
+ doc = elem.ownerDocument,
+ nodeName = elem.nodeName,
+ display = defaultDisplayMap[ nodeName ];
+
+ if ( display ) {
+ return display;
+ }
+
+ temp = doc.body.appendChild( doc.createElement( nodeName ) );
+ display = jQuery.css( temp, "display" );
+
+ temp.parentNode.removeChild( temp );
+
+ if ( display === "none" ) {
+ display = "block";
+ }
+ defaultDisplayMap[ nodeName ] = display;
+
+ return display;
+}
+
+function showHide( elements, show ) {
+ var display, elem,
+ values = [],
+ index = 0,
+ length = elements.length;
+
+ // Determine new display value for elements that need to change
+ for ( ; index < length; index++ ) {
+ elem = elements[ index ];
+ if ( !elem.style ) {
+ continue;
+ }
+
+ display = elem.style.display;
+ if ( show ) {
+
+ // Since we force visibility upon cascade-hidden elements, an immediate (and slow)
+ // check is required in this first loop unless we have a nonempty display value (either
+ // inline or about-to-be-restored)
+ if ( display === "none" ) {
+ values[ index ] = dataPriv.get( elem, "display" ) || null;
+ if ( !values[ index ] ) {
+ elem.style.display = "";
+ }
+ }
+ if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) {
+ values[ index ] = getDefaultDisplay( elem );
+ }
+ } else {
+ if ( display !== "none" ) {
+ values[ index ] = "none";
+
+ // Remember what we're overwriting
+ dataPriv.set( elem, "display", display );
+ }
+ }
+ }
+
+ // Set the display of the elements in a second loop to avoid constant reflow
+ for ( index = 0; index < length; index++ ) {
+ if ( values[ index ] != null ) {
+ elements[ index ].style.display = values[ index ];
+ }
+ }
+
+ return elements;
+}
+
+jQuery.fn.extend( {
+ show: function() {
+ return showHide( this, true );
+ },
+ hide: function() {
+ return showHide( this );
+ },
+ toggle: function( state ) {
+ if ( typeof state === "boolean" ) {
+ return state ? this.show() : this.hide();
+ }
+
+ return this.each( function() {
+ if ( isHiddenWithinTree( this ) ) {
+ jQuery( this ).show();
+ } else {
+ jQuery( this ).hide();
+ }
+ } );
+ }
+} );
+var rcheckableType = ( /^(?:checkbox|radio)$/i );
+
+var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i );
+
+var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i );
+
+
+
+( function() {
+ var fragment = document.createDocumentFragment(),
+ div = fragment.appendChild( document.createElement( "div" ) ),
+ input = document.createElement( "input" );
+
+ // Support: Android 4.0 - 4.3 only
+ // Check state lost if the name is set (trac-11217)
+ // Support: Windows Web Apps (WWA)
+ // `name` and `type` must use .setAttribute for WWA (trac-14901)
+ input.setAttribute( "type", "radio" );
+ input.setAttribute( "checked", "checked" );
+ input.setAttribute( "name", "t" );
+
+ div.appendChild( input );
+
+ // Support: Android <=4.1 only
+ // Older WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ // Support: IE <=11 only
+ // Make sure textarea (and checkbox) defaultValue is properly cloned
+ div.innerHTML = "<textarea>x</textarea>";
+ support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
+
+ // Support: IE <=9 only
+ // IE <=9 replaces <option> tags with their contents when inserted outside of
+ // the select element.
+ div.innerHTML = "<option></option>";
+ support.option = !!div.lastChild;
+} )();
+
+
+// We have to close these tags to support XHTML (trac-13200)
+var wrapMap = {
+
+ // XHTML parsers do not magically insert elements in the
+ // same way that tag soup parsers do. So we cannot shorten
+ // this by omitting <tbody> or other required elements.
+ thead: [ 1, "<table>", "</table>" ],
+ col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
+ tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+ td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+
+ _default: [ 0, "", "" ]
+};
+
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// Support: IE <=9 only
+if ( !support.option ) {
+ wrapMap.optgroup = wrapMap.option = [ 1, "<select multiple='multiple'>", "</select>" ];
+}
+
+
+function getAll( context, tag ) {
+
+ // Support: IE <=9 - 11 only
+ // Use typeof to avoid zero-argument method invocation on host objects (trac-15151)
+ var ret;
+
+ if ( typeof context.getElementsByTagName !== "undefined" ) {
+ ret = context.getElementsByTagName( tag || "*" );
+
+ } else if ( typeof context.querySelectorAll !== "undefined" ) {
+ ret = context.querySelectorAll( tag || "*" );
+
+ } else {
+ ret = [];
+ }
+
+ if ( tag === undefined || tag && nodeName( context, tag ) ) {
+ return jQuery.merge( [ context ], ret );
+ }
+
+ return ret;
+}
+
+
+// Mark scripts as having already been evaluated
+function setGlobalEval( elems, refElements ) {
+ var i = 0,
+ l = elems.length;
+
+ for ( ; i < l; i++ ) {
+ dataPriv.set(
+ elems[ i ],
+ "globalEval",
+ !refElements || dataPriv.get( refElements[ i ], "globalEval" )
+ );
+ }
+}
+
+
+var rhtml = /<|&#?\w+;/;
+
+function buildFragment( elems, context, scripts, selection, ignored ) {
+ var elem, tmp, tag, wrap, attached, j,
+ fragment = context.createDocumentFragment(),
+ nodes = [],
+ i = 0,
+ l = elems.length;
+
+ for ( ; i < l; i++ ) {
+ elem = elems[ i ];
+
+ if ( elem || elem === 0 ) {
+
+ // Add nodes directly
+ if ( toType( elem ) === "object" ) {
+
+ // Support: Android <=4.0 only, PhantomJS 1 only
+ // push.apply(_, arraylike) throws on ancient WebKit
+ jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
+
+ // Convert non-html into a text node
+ } else if ( !rhtml.test( elem ) ) {
+ nodes.push( context.createTextNode( elem ) );
+
+ // Convert html into DOM nodes
+ } else {
+ tmp = tmp || fragment.appendChild( context.createElement( "div" ) );
+
+ // Deserialize a standard representation
+ tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
+ wrap = wrapMap[ tag ] || wrapMap._default;
+ tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];
+
+ // Descend through wrappers to the right content
+ j = wrap[ 0 ];
+ while ( j-- ) {
+ tmp = tmp.lastChild;
+ }
+
+ // Support: Android <=4.0 only, PhantomJS 1 only
+ // push.apply(_, arraylike) throws on ancient WebKit
+ jQuery.merge( nodes, tmp.childNodes );
+
+ // Remember the top-level container
+ tmp = fragment.firstChild;
+
+ // Ensure the created nodes are orphaned (trac-12392)
+ tmp.textContent = "";
+ }
+ }
+ }
+
+ // Remove wrapper from fragment
+ fragment.textContent = "";
+
+ i = 0;
+ while ( ( elem = nodes[ i++ ] ) ) {
+
+ // Skip elements already in the context collection (trac-4087)
+ if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
+ if ( ignored ) {
+ ignored.push( elem );
+ }
+ continue;
+ }
+
+ attached = isAttached( elem );
+
+ // Append to fragment
+ tmp = getAll( fragment.appendChild( elem ), "script" );
+
+ // Preserve script evaluation history
+ if ( attached ) {
+ setGlobalEval( tmp );
+ }
+
+ // Capture executables
+ if ( scripts ) {
+ j = 0;
+ while ( ( elem = tmp[ j++ ] ) ) {
+ if ( rscriptType.test( elem.type || "" ) ) {
+ scripts.push( elem );
+ }
+ }
+ }
+ }
+
+ return fragment;
+}
+
+
+var rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
+
+function returnTrue() {
+ return true;
+}
+
+function returnFalse() {
+ return false;
+}
+
+function on( elem, types, selector, data, fn, one ) {
+ var origFn, type;
+
+ // Types can be a map of types/handlers
+ if ( typeof types === "object" ) {
+
+ // ( types-Object, selector, data )
+ if ( typeof selector !== "string" ) {
+
+ // ( types-Object, data )
+ data = data || selector;
+ selector = undefined;
+ }
+ for ( type in types ) {
+ on( elem, type, selector, data, types[ type ], one );
+ }
+ return elem;
+ }
+
+ if ( data == null && fn == null ) {
+
+ // ( types, fn )
+ fn = selector;
+ data = selector = undefined;
+ } else if ( fn == null ) {
+ if ( typeof selector === "string" ) {
+
+ // ( types, selector, fn )
+ fn = data;
+ data = undefined;
+ } else {
+
+ // ( types, data, fn )
+ fn = data;
+ data = selector;
+ selector = undefined;
+ }
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ } else if ( !fn ) {
+ return elem;
+ }
+
+ if ( one === 1 ) {
+ origFn = fn;
+ fn = function( event ) {
+
+ // Can use an empty set, since event contains the info
+ jQuery().off( event );
+ return origFn.apply( this, arguments );
+ };
+
+ // Use same guid so caller can remove using origFn
+ fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+ }
+ return elem.each( function() {
+ jQuery.event.add( this, types, fn, data, selector );
+ } );
+}
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+ global: {},
+
+ add: function( elem, types, handler, data, selector ) {
+
+ var handleObjIn, eventHandle, tmp,
+ events, t, handleObj,
+ special, handlers, type, namespaces, origType,
+ elemData = dataPriv.get( elem );
+
+ // Only attach events to objects that accept data
+ if ( !acceptData( elem ) ) {
+ return;
+ }
+
+ // Caller can pass in an object of custom data in lieu of the handler
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ selector = handleObjIn.selector;
+ }
+
+ // Ensure that invalid selectors throw exceptions at attach time
+ // Evaluate against documentElement in case elem is a non-element node (e.g., document)
+ if ( selector ) {
+ jQuery.find.matchesSelector( documentElement, selector );
+ }
+
+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure and main handler, if this is the first
+ if ( !( events = elemData.events ) ) {
+ events = elemData.events = Object.create( null );
+ }
+ if ( !( eventHandle = elemData.handle ) ) {
+ eventHandle = elemData.handle = function( e ) {
+
+ // Discard the second event of a jQuery.event.trigger() and
+ // when an event is called after a page has unloaded
+ return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
+ jQuery.event.dispatch.apply( elem, arguments ) : undefined;
+ };
+ }
+
+ // Handle multiple events separated by a space
+ types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[ t ] ) || [];
+ type = origType = tmp[ 1 ];
+ namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
+
+ // There *must* be a type, no attaching namespace-only handlers
+ if ( !type ) {
+ continue;
+ }
+
+ // If event changes its type, use the special event handlers for the changed type
+ special = jQuery.event.special[ type ] || {};
+
+ // If selector defined, determine special event api type, otherwise given type
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+
+ // Update special based on newly reset type
+ special = jQuery.event.special[ type ] || {};
+
+ // handleObj is passed to all event handlers
+ handleObj = jQuery.extend( {
+ type: type,
+ origType: origType,
+ data: data,
+ handler: handler,
+ guid: handler.guid,
+ selector: selector,
+ needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
+ namespace: namespaces.join( "." )
+ }, handleObjIn );
+
+ // Init the event handler queue if we're the first
+ if ( !( handlers = events[ type ] ) ) {
+ handlers = events[ type ] = [];
+ handlers.delegateCount = 0;
+
+ // Only use addEventListener if the special events handler returns false
+ if ( !special.setup ||
+ special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add to the element's handler list, delegates in front
+ if ( selector ) {
+ handlers.splice( handlers.delegateCount++, 0, handleObj );
+ } else {
+ handlers.push( handleObj );
+ }
+
+ // Keep track of which events have ever been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
+
+ },
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, selector, mappedTypes ) {
+
+ var j, origCount, tmp,
+ events, t, handleObj,
+ special, handlers, type, namespaces, origType,
+ elemData = dataPriv.hasData( elem ) && dataPriv.get( elem );
+
+ if ( !elemData || !( events = elemData.events ) ) {
+ return;
+ }
+
+ // Once for each type.namespace in types; type may be omitted
+ types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[ t ] ) || [];
+ type = origType = tmp[ 1 ];
+ namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
+
+ // Unbind all events (on this namespace, if provided) for the element
+ if ( !type ) {
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+ }
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+ handlers = events[ type ] || [];
+ tmp = tmp[ 2 ] &&
+ new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" );
+
+ // Remove matching events
+ origCount = j = handlers.length;
+ while ( j-- ) {
+ handleObj = handlers[ j ];
+
+ if ( ( mappedTypes || origType === handleObj.origType ) &&
+ ( !handler || handler.guid === handleObj.guid ) &&
+ ( !tmp || tmp.test( handleObj.namespace ) ) &&
+ ( !selector || selector === handleObj.selector ||
+ selector === "**" && handleObj.selector ) ) {
+ handlers.splice( j, 1 );
+
+ if ( handleObj.selector ) {
+ handlers.delegateCount--;
+ }
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+ }
+
+ // Remove generic event handler if we removed something and no more handlers exist
+ // (avoids potential for endless recursion during removal of special event handlers)
+ if ( origCount && !handlers.length ) {
+ if ( !special.teardown ||
+ special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
+
+ delete events[ type ];
+ }
+ }
+
+ // Remove data and the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ dataPriv.remove( elem, "handle events" );
+ }
+ },
+
+ dispatch: function( nativeEvent ) {
+
+ var i, j, ret, matched, handleObj, handlerQueue,
+ args = new Array( arguments.length ),
+
+ // Make a writable jQuery.Event from the native event object
+ event = jQuery.event.fix( nativeEvent ),
+
+ handlers = (
+ dataPriv.get( this, "events" ) || Object.create( null )
+ )[ event.type ] || [],
+ special = jQuery.event.special[ event.type ] || {};
+
+ // Use the fix-ed jQuery.Event rather than the (read-only) native event
+ args[ 0 ] = event;
+
+ for ( i = 1; i < arguments.length; i++ ) {
+ args[ i ] = arguments[ i ];
+ }
+
+ event.delegateTarget = this;
+
+ // Call the preDispatch hook for the mapped type, and let it bail if desired
+ if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+ return;
+ }
+
+ // Determine handlers
+ handlerQueue = jQuery.event.handlers.call( this, event, handlers );
+
+ // Run delegates first; they may want to stop propagation beneath us
+ i = 0;
+ while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
+ event.currentTarget = matched.elem;
+
+ j = 0;
+ while ( ( handleObj = matched.handlers[ j++ ] ) &&
+ !event.isImmediatePropagationStopped() ) {
+
+ // If the event is namespaced, then each handler is only invoked if it is
+ // specially universal or its namespaces are a superset of the event's.
+ if ( !event.rnamespace || handleObj.namespace === false ||
+ event.rnamespace.test( handleObj.namespace ) ) {
+
+ event.handleObj = handleObj;
+ event.data = handleObj.data;
+
+ ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
+ handleObj.handler ).apply( matched.elem, args );
+
+ if ( ret !== undefined ) {
+ if ( ( event.result = ret ) === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
+
+ // Call the postDispatch hook for the mapped type
+ if ( special.postDispatch ) {
+ special.postDispatch.call( this, event );
+ }
+
+ return event.result;
+ },
+
+ handlers: function( event, handlers ) {
+ var i, handleObj, sel, matchedHandlers, matchedSelectors,
+ handlerQueue = [],
+ delegateCount = handlers.delegateCount,
+ cur = event.target;
+
+ // Find delegate handlers
+ if ( delegateCount &&
+
+ // Support: IE <=9
+ // Black-hole SVG <use> instance trees (trac-13180)
+ cur.nodeType &&
+
+ // Support: Firefox <=42
+ // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861)
+ // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click
+ // Support: IE 11 only
+ // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343)
+ !( event.type === "click" && event.button >= 1 ) ) {
+
+ for ( ; cur !== this; cur = cur.parentNode || this ) {
+
+ // Don't check non-elements (trac-13208)
+ // Don't process clicks on disabled elements (trac-6911, trac-8165, trac-11382, trac-11764)
+ if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) {
+ matchedHandlers = [];
+ matchedSelectors = {};
+ for ( i = 0; i < delegateCount; i++ ) {
+ handleObj = handlers[ i ];
+
+ // Don't conflict with Object.prototype properties (trac-13203)
+ sel = handleObj.selector + " ";
+
+ if ( matchedSelectors[ sel ] === undefined ) {
+ matchedSelectors[ sel ] = handleObj.needsContext ?
+ jQuery( sel, this ).index( cur ) > -1 :
+ jQuery.find( sel, this, null, [ cur ] ).length;
+ }
+ if ( matchedSelectors[ sel ] ) {
+ matchedHandlers.push( handleObj );
+ }
+ }
+ if ( matchedHandlers.length ) {
+ handlerQueue.push( { elem: cur, handlers: matchedHandlers } );
+ }
+ }
+ }
+ }
+
+ // Add the remaining (directly-bound) handlers
+ cur = this;
+ if ( delegateCount < handlers.length ) {
+ handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } );
+ }
+
+ return handlerQueue;
+ },
+
+ addProp: function( name, hook ) {
+ Object.defineProperty( jQuery.Event.prototype, name, {
+ enumerable: true,
+ configurable: true,
+
+ get: isFunction( hook ) ?
+ function() {
+ if ( this.originalEvent ) {
+ return hook( this.originalEvent );
+ }
+ } :
+ function() {
+ if ( this.originalEvent ) {
+ return this.originalEvent[ name ];
+ }
+ },
+
+ set: function( value ) {
+ Object.defineProperty( this, name, {
+ enumerable: true,
+ configurable: true,
+ writable: true,
+ value: value
+ } );
+ }
+ } );
+ },
+
+ fix: function( originalEvent ) {
+ return originalEvent[ jQuery.expando ] ?
+ originalEvent :
+ new jQuery.Event( originalEvent );
+ },
+
+ special: {
+ load: {
+
+ // Prevent triggered image.load events from bubbling to window.load
+ noBubble: true
+ },
+ click: {
+
+ // Utilize native event to ensure correct state for checkable inputs
+ setup: function( data ) {
+
+ // For mutual compressibility with _default, replace `this` access with a local var.
+ // `|| data` is dead code meant only to preserve the variable through minification.
+ var el = this || data;
+
+ // Claim the first handler
+ if ( rcheckableType.test( el.type ) &&
+ el.click && nodeName( el, "input" ) ) {
+
+ // dataPriv.set( el, "click", ... )
+ leverageNative( el, "click", true );
+ }
+
+ // Return false to allow normal processing in the caller
+ return false;
+ },
+ trigger: function( data ) {
+
+ // For mutual compressibility with _default, replace `this` access with a local var.
+ // `|| data` is dead code meant only to preserve the variable through minification.
+ var el = this || data;
+
+ // Force setup before triggering a click
+ if ( rcheckableType.test( el.type ) &&
+ el.click && nodeName( el, "input" ) ) {
+
+ leverageNative( el, "click" );
+ }
+
+ // Return non-false to allow normal event-path propagation
+ return true;
+ },
+
+ // For cross-browser consistency, suppress native .click() on links
+ // Also prevent it if we're currently inside a leveraged native-event stack
+ _default: function( event ) {
+ var target = event.target;
+ return rcheckableType.test( target.type ) &&
+ target.click && nodeName( target, "input" ) &&
+ dataPriv.get( target, "click" ) ||
+ nodeName( target, "a" );
+ }
+ },
+
+ beforeunload: {
+ postDispatch: function( event ) {
+
+ // Support: Firefox 20+
+ // Firefox doesn't alert if the returnValue field is not set.
+ if ( event.result !== undefined && event.originalEvent ) {
+ event.originalEvent.returnValue = event.result;
+ }
+ }
+ }
+ }
+};
+
+// Ensure the presence of an event listener that handles manually-triggered
+// synthetic events by interrupting progress until reinvoked in response to
+// *native* events that it fires directly, ensuring that state changes have
+// already occurred before other listeners are invoked.
+function leverageNative( el, type, isSetup ) {
+
+ // Missing `isSetup` indicates a trigger call, which must force setup through jQuery.event.add
+ if ( !isSetup ) {
+ if ( dataPriv.get( el, type ) === undefined ) {
+ jQuery.event.add( el, type, returnTrue );
+ }
+ return;
+ }
+
+ // Register the controller as a special universal handler for all event namespaces
+ dataPriv.set( el, type, false );
+ jQuery.event.add( el, type, {
+ namespace: false,
+ handler: function( event ) {
+ var result,
+ saved = dataPriv.get( this, type );
+
+ if ( ( event.isTrigger & 1 ) && this[ type ] ) {
+
+ // Interrupt processing of the outer synthetic .trigger()ed event
+ if ( !saved ) {
+
+ // Store arguments for use when handling the inner native event
+ // There will always be at least one argument (an event object), so this array
+ // will not be confused with a leftover capture object.
+ saved = slice.call( arguments );
+ dataPriv.set( this, type, saved );
+
+ // Trigger the native event and capture its result
+ this[ type ]();
+ result = dataPriv.get( this, type );
+ dataPriv.set( this, type, false );
+
+ if ( saved !== result ) {
+
+ // Cancel the outer synthetic event
+ event.stopImmediatePropagation();
+ event.preventDefault();
+
+ return result;
+ }
+
+ // If this is an inner synthetic event for an event with a bubbling surrogate
+ // (focus or blur), assume that the surrogate already propagated from triggering
+ // the native event and prevent that from happening again here.
+ // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the
+ // bubbling surrogate propagates *after* the non-bubbling base), but that seems
+ // less bad than duplication.
+ } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) {
+ event.stopPropagation();
+ }
+
+ // If this is a native event triggered above, everything is now in order
+ // Fire an inner synthetic event with the original arguments
+ } else if ( saved ) {
+
+ // ...and capture the result
+ dataPriv.set( this, type, jQuery.event.trigger(
+ saved[ 0 ],
+ saved.slice( 1 ),
+ this
+ ) );
+
+ // Abort handling of the native event by all jQuery handlers while allowing
+ // native handlers on the same element to run. On target, this is achieved
+ // by stopping immediate propagation just on the jQuery event. However,
+ // the native event is re-wrapped by a jQuery one on each level of the
+ // propagation so the only way to stop it for jQuery is to stop it for
+ // everyone via native `stopPropagation()`. This is not a problem for
+ // focus/blur which don't bubble, but it does also stop click on checkboxes
+ // and radios. We accept this limitation.
+ event.stopPropagation();
+ event.isImmediatePropagationStopped = returnTrue;
+ }
+ }
+ } );
+}
+
+jQuery.removeEvent = function( elem, type, handle ) {
+
+ // This "if" is needed for plain objects
+ if ( elem.removeEventListener ) {
+ elem.removeEventListener( type, handle );
+ }
+};
+
+jQuery.Event = function( src, props ) {
+
+ // Allow instantiation without the 'new' keyword
+ if ( !( this instanceof jQuery.Event ) ) {
+ return new jQuery.Event( src, props );
+ }
+
+ // Event object
+ if ( src && src.type ) {
+ this.originalEvent = src;
+ this.type = src.type;
+
+ // Events bubbling up the document may have been marked as prevented
+ // by a handler lower down the tree; reflect the correct value.
+ this.isDefaultPrevented = src.defaultPrevented ||
+ src.defaultPrevented === undefined &&
+
+ // Support: Android <=2.3 only
+ src.returnValue === false ?
+ returnTrue :
+ returnFalse;
+
+ // Create target properties
+ // Support: Safari <=6 - 7 only
+ // Target should not be a text node (trac-504, trac-13143)
+ this.target = ( src.target && src.target.nodeType === 3 ) ?
+ src.target.parentNode :
+ src.target;
+
+ this.currentTarget = src.currentTarget;
+ this.relatedTarget = src.relatedTarget;
+
+ // Event type
+ } else {
+ this.type = src;
+ }
+
+ // Put explicitly provided properties onto the event object
+ if ( props ) {
+ jQuery.extend( this, props );
+ }
+
+ // Create a timestamp if incoming event doesn't have one
+ this.timeStamp = src && src.timeStamp || Date.now();
+
+ // Mark it as fixed
+ this[ jQuery.expando ] = true;
+};
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+ constructor: jQuery.Event,
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse,
+ isSimulated: false,
+
+ preventDefault: function() {
+ var e = this.originalEvent;
+
+ this.isDefaultPrevented = returnTrue;
+
+ if ( e && !this.isSimulated ) {
+ e.preventDefault();
+ }
+ },
+ stopPropagation: function() {
+ var e = this.originalEvent;
+
+ this.isPropagationStopped = returnTrue;
+
+ if ( e && !this.isSimulated ) {
+ e.stopPropagation();
+ }
+ },
+ stopImmediatePropagation: function() {
+ var e = this.originalEvent;
+
+ this.isImmediatePropagationStopped = returnTrue;
+
+ if ( e && !this.isSimulated ) {
+ e.stopImmediatePropagation();
+ }
+
+ this.stopPropagation();
+ }
+};
+
+// Includes all common event props including KeyEvent and MouseEvent specific props
+jQuery.each( {
+ altKey: true,
+ bubbles: true,
+ cancelable: true,
+ changedTouches: true,
+ ctrlKey: true,
+ detail: true,
+ eventPhase: true,
+ metaKey: true,
+ pageX: true,
+ pageY: true,
+ shiftKey: true,
+ view: true,
+ "char": true,
+ code: true,
+ charCode: true,
+ key: true,
+ keyCode: true,
+ button: true,
+ buttons: true,
+ clientX: true,
+ clientY: true,
+ offsetX: true,
+ offsetY: true,
+ pointerId: true,
+ pointerType: true,
+ screenX: true,
+ screenY: true,
+ targetTouches: true,
+ toElement: true,
+ touches: true,
+ which: true
+}, jQuery.event.addProp );
+
+jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) {
+
+ function focusMappedHandler( nativeEvent ) {
+ if ( document.documentMode ) {
+
+ // Support: IE 11+
+ // Attach a single focusin/focusout handler on the document while someone wants
+ // focus/blur. This is because the former are synchronous in IE while the latter
+ // are async. In other browsers, all those handlers are invoked synchronously.
+
+ // `handle` from private data would already wrap the event, but we need
+ // to change the `type` here.
+ var handle = dataPriv.get( this, "handle" ),
+ event = jQuery.event.fix( nativeEvent );
+ event.type = nativeEvent.type === "focusin" ? "focus" : "blur";
+ event.isSimulated = true;
+
+ // First, handle focusin/focusout
+ handle( nativeEvent );
+
+ // ...then, handle focus/blur
+ //
+ // focus/blur don't bubble while focusin/focusout do; simulate the former by only
+ // invoking the handler at the lower level.
+ if ( event.target === event.currentTarget ) {
+
+ // The setup part calls `leverageNative`, which, in turn, calls
+ // `jQuery.event.add`, so event handle will already have been set
+ // by this point.
+ handle( event );
+ }
+ } else {
+
+ // For non-IE browsers, attach a single capturing handler on the document
+ // while someone wants focusin/focusout.
+ jQuery.event.simulate( delegateType, nativeEvent.target,
+ jQuery.event.fix( nativeEvent ) );
+ }
+ }
+
+ jQuery.event.special[ type ] = {
+
+ // Utilize native event if possible so blur/focus sequence is correct
+ setup: function() {
+
+ var attaches;
+
+ // Claim the first handler
+ // dataPriv.set( this, "focus", ... )
+ // dataPriv.set( this, "blur", ... )
+ leverageNative( this, type, true );
+
+ if ( document.documentMode ) {
+
+ // Support: IE 9 - 11+
+ // We use the same native handler for focusin & focus (and focusout & blur)
+ // so we need to coordinate setup & teardown parts between those events.
+ // Use `delegateType` as the key as `type` is already used by `leverageNative`.
+ attaches = dataPriv.get( this, delegateType );
+ if ( !attaches ) {
+ this.addEventListener( delegateType, focusMappedHandler );
+ }
+ dataPriv.set( this, delegateType, ( attaches || 0 ) + 1 );
+ } else {
+
+ // Return false to allow normal processing in the caller
+ return false;
+ }
+ },
+ trigger: function() {
+
+ // Force setup before trigger
+ leverageNative( this, type );
+
+ // Return non-false to allow normal event-path propagation
+ return true;
+ },
+
+ teardown: function() {
+ var attaches;
+
+ if ( document.documentMode ) {
+ attaches = dataPriv.get( this, delegateType ) - 1;
+ if ( !attaches ) {
+ this.removeEventListener( delegateType, focusMappedHandler );
+ dataPriv.remove( this, delegateType );
+ } else {
+ dataPriv.set( this, delegateType, attaches );
+ }
+ } else {
+
+ // Return false to indicate standard teardown should be applied
+ return false;
+ }
+ },
+
+ // Suppress native focus or blur if we're currently inside
+ // a leveraged native-event stack
+ _default: function( event ) {
+ return dataPriv.get( event.target, type );
+ },
+
+ delegateType: delegateType
+ };
+
+ // Support: Firefox <=44
+ // Firefox doesn't have focus(in | out) events
+ // Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787
+ //
+ // Support: Chrome <=48 - 49, Safari <=9.0 - 9.1
+ // focus(in | out) events fire after focus & blur events,
+ // which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order
+ // Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857
+ //
+ // Support: IE 9 - 11+
+ // To preserve relative focusin/focus & focusout/blur event order guaranteed on the 3.x branch,
+ // attach a single handler for both events in IE.
+ jQuery.event.special[ delegateType ] = {
+ setup: function() {
+
+ // Handle: regular nodes (via `this.ownerDocument`), window
+ // (via `this.document`) & document (via `this`).
+ var doc = this.ownerDocument || this.document || this,
+ dataHolder = document.documentMode ? this : doc,
+ attaches = dataPriv.get( dataHolder, delegateType );
+
+ // Support: IE 9 - 11+
+ // We use the same native handler for focusin & focus (and focusout & blur)
+ // so we need to coordinate setup & teardown parts between those events.
+ // Use `delegateType` as the key as `type` is already used by `leverageNative`.
+ if ( !attaches ) {
+ if ( document.documentMode ) {
+ this.addEventListener( delegateType, focusMappedHandler );
+ } else {
+ doc.addEventListener( type, focusMappedHandler, true );
+ }
+ }
+ dataPriv.set( dataHolder, delegateType, ( attaches || 0 ) + 1 );
+ },
+ teardown: function() {
+ var doc = this.ownerDocument || this.document || this,
+ dataHolder = document.documentMode ? this : doc,
+ attaches = dataPriv.get( dataHolder, delegateType ) - 1;
+
+ if ( !attaches ) {
+ if ( document.documentMode ) {
+ this.removeEventListener( delegateType, focusMappedHandler );
+ } else {
+ doc.removeEventListener( type, focusMappedHandler, true );
+ }
+ dataPriv.remove( dataHolder, delegateType );
+ } else {
+ dataPriv.set( dataHolder, delegateType, attaches );
+ }
+ }
+ };
+} );
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+// so that event delegation works in jQuery.
+// Do the same for pointerenter/pointerleave and pointerover/pointerout
+//
+// Support: Safari 7 only
+// Safari sends mouseenter too often; see:
+// https://bugs.chromium.org/p/chromium/issues/detail?id=470258
+// for the description of the bug (it existed in older Chrome versions as well).
+jQuery.each( {
+ mouseenter: "mouseover",
+ mouseleave: "mouseout",
+ pointerenter: "pointerover",
+ pointerleave: "pointerout"
+}, function( orig, fix ) {
+ jQuery.event.special[ orig ] = {
+ delegateType: fix,
+ bindType: fix,
+
+ handle: function( event ) {
+ var ret,
+ target = this,
+ related = event.relatedTarget,
+ handleObj = event.handleObj;
+
+ // For mouseenter/leave call the handler if related is outside the target.
+ // NB: No relatedTarget if the mouse left/entered the browser window
+ if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) {
+ event.type = handleObj.origType;
+ ret = handleObj.handler.apply( this, arguments );
+ event.type = fix;
+ }
+ return ret;
+ }
+ };
+} );
+
+jQuery.fn.extend( {
+
+ on: function( types, selector, data, fn ) {
+ return on( this, types, selector, data, fn );
+ },
+ one: function( types, selector, data, fn ) {
+ return on( this, types, selector, data, fn, 1 );
+ },
+ off: function( types, selector, fn ) {
+ var handleObj, type;
+ if ( types && types.preventDefault && types.handleObj ) {
+
+ // ( event ) dispatched jQuery.Event
+ handleObj = types.handleObj;
+ jQuery( types.delegateTarget ).off(
+ handleObj.namespace ?
+ handleObj.origType + "." + handleObj.namespace :
+ handleObj.origType,
+ handleObj.selector,
+ handleObj.handler
+ );
+ return this;
+ }
+ if ( typeof types === "object" ) {
+
+ // ( types-object [, selector] )
+ for ( type in types ) {
+ this.off( type, selector, types[ type ] );
+ }
+ return this;
+ }
+ if ( selector === false || typeof selector === "function" ) {
+
+ // ( types [, fn] )
+ fn = selector;
+ selector = undefined;
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ }
+ return this.each( function() {
+ jQuery.event.remove( this, types, fn, selector );
+ } );
+ }
+} );
+
+
+var
+
+ // Support: IE <=10 - 11, Edge 12 - 13 only
+ // In IE/Edge using regex groups here causes severe slowdowns.
+ // See https://connect.microsoft.com/IE/feedback/details/1736512/
+ rnoInnerhtml = /<script|<style|<link/i,
+
+ // checked="checked" or checked
+ rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+
+ rcleanScript = /^\s*<!\[CDATA\[|\]\]>\s*$/g;
+
+// Prefer a tbody over its parent table for containing new rows
+function manipulationTarget( elem, content ) {
+ if ( nodeName( elem, "table" ) &&
+ nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) {
+
+ return jQuery( elem ).children( "tbody" )[ 0 ] || elem;
+ }
+
+ return elem;
+}
+
+// Replace/restore the type attribute of script elements for safe DOM manipulation
+function disableScript( elem ) {
+ elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type;
+ return elem;
+}
+function restoreScript( elem ) {
+ if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) {
+ elem.type = elem.type.slice( 5 );
+ } else {
+ elem.removeAttribute( "type" );
+ }
+
+ return elem;
+}
+
+function cloneCopyEvent( src, dest ) {
+ var i, l, type, pdataOld, udataOld, udataCur, events;
+
+ if ( dest.nodeType !== 1 ) {
+ return;
+ }
+
+ // 1. Copy private data: events, handlers, etc.
+ if ( dataPriv.hasData( src ) ) {
+ pdataOld = dataPriv.get( src );
+ events = pdataOld.events;
+
+ if ( events ) {
+ dataPriv.remove( dest, "handle events" );
+
+ for ( type in events ) {
+ for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+ jQuery.event.add( dest, type, events[ type ][ i ] );
+ }
+ }
+ }
+ }
+
+ // 2. Copy user data
+ if ( dataUser.hasData( src ) ) {
+ udataOld = dataUser.access( src );
+ udataCur = jQuery.extend( {}, udataOld );
+
+ dataUser.set( dest, udataCur );
+ }
+}
+
+// Fix IE bugs, see support tests
+function fixInput( src, dest ) {
+ var nodeName = dest.nodeName.toLowerCase();
+
+ // Fails to persist the checked state of a cloned checkbox or radio button.
+ if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
+ dest.checked = src.checked;
+
+ // Fails to return the selected option to the default selected state when cloning options
+ } else if ( nodeName === "input" || nodeName === "textarea" ) {
+ dest.defaultValue = src.defaultValue;
+ }
+}
+
+function domManip( collection, args, callback, ignored ) {
+
+ // Flatten any nested arrays
+ args = flat( args );
+
+ var fragment, first, scripts, hasScripts, node, doc,
+ i = 0,
+ l = collection.length,
+ iNoClone = l - 1,
+ value = args[ 0 ],
+ valueIsFunction = isFunction( value );
+
+ // We can't cloneNode fragments that contain checked, in WebKit
+ if ( valueIsFunction ||
+ ( l > 1 && typeof value === "string" &&
+ !support.checkClone && rchecked.test( value ) ) ) {
+ return collection.each( function( index ) {
+ var self = collection.eq( index );
+ if ( valueIsFunction ) {
+ args[ 0 ] = value.call( this, index, self.html() );
+ }
+ domManip( self, args, callback, ignored );
+ } );
+ }
+
+ if ( l ) {
+ fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored );
+ first = fragment.firstChild;
+
+ if ( fragment.childNodes.length === 1 ) {
+ fragment = first;
+ }
+
+ // Require either new content or an interest in ignored elements to invoke the callback
+ if ( first || ignored ) {
+ scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
+ hasScripts = scripts.length;
+
+ // Use the original fragment for the last item
+ // instead of the first because it can end up
+ // being emptied incorrectly in certain situations (trac-8070).
+ for ( ; i < l; i++ ) {
+ node = fragment;
+
+ if ( i !== iNoClone ) {
+ node = jQuery.clone( node, true, true );
+
+ // Keep references to cloned scripts for later restoration
+ if ( hasScripts ) {
+
+ // Support: Android <=4.0 only, PhantomJS 1 only
+ // push.apply(_, arraylike) throws on ancient WebKit
+ jQuery.merge( scripts, getAll( node, "script" ) );
+ }
+ }
+
+ callback.call( collection[ i ], node, i );
+ }
+
+ if ( hasScripts ) {
+ doc = scripts[ scripts.length - 1 ].ownerDocument;
+
+ // Re-enable scripts
+ jQuery.map( scripts, restoreScript );
+
+ // Evaluate executable scripts on first document insertion
+ for ( i = 0; i < hasScripts; i++ ) {
+ node = scripts[ i ];
+ if ( rscriptType.test( node.type || "" ) &&
+ !dataPriv.access( node, "globalEval" ) &&
+ jQuery.contains( doc, node ) ) {
+
+ if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) {
+
+ // Optional AJAX dependency, but won't run scripts if not present
+ if ( jQuery._evalUrl && !node.noModule ) {
+ jQuery._evalUrl( node.src, {
+ nonce: node.nonce || node.getAttribute( "nonce" )
+ }, doc );
+ }
+ } else {
+
+ // Unwrap a CDATA section containing script contents. This shouldn't be
+ // needed as in XML documents they're already not visible when
+ // inspecting element contents and in HTML documents they have no
+ // meaning but we're preserving that logic for backwards compatibility.
+ // This will be removed completely in 4.0. See gh-4904.
+ DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return collection;
+}
+
+function remove( elem, selector, keepData ) {
+ var node,
+ nodes = selector ? jQuery.filter( selector, elem ) : elem,
+ i = 0;
+
+ for ( ; ( node = nodes[ i ] ) != null; i++ ) {
+ if ( !keepData && node.nodeType === 1 ) {
+ jQuery.cleanData( getAll( node ) );
+ }
+
+ if ( node.parentNode ) {
+ if ( keepData && isAttached( node ) ) {
+ setGlobalEval( getAll( node, "script" ) );
+ }
+ node.parentNode.removeChild( node );
+ }
+ }
+
+ return elem;
+}
+
+jQuery.extend( {
+ htmlPrefilter: function( html ) {
+ return html;
+ },
+
+ clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+ var i, l, srcElements, destElements,
+ clone = elem.cloneNode( true ),
+ inPage = isAttached( elem );
+
+ // Fix IE cloning issues
+ if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&
+ !jQuery.isXMLDoc( elem ) ) {
+
+ // We eschew jQuery#find here for performance reasons:
+ // https://jsperf.com/getall-vs-sizzle/2
+ destElements = getAll( clone );
+ srcElements = getAll( elem );
+
+ for ( i = 0, l = srcElements.length; i < l; i++ ) {
+ fixInput( srcElements[ i ], destElements[ i ] );
+ }
+ }
+
+ // Copy the events from the original to the clone
+ if ( dataAndEvents ) {
+ if ( deepDataAndEvents ) {
+ srcElements = srcElements || getAll( elem );
+ destElements = destElements || getAll( clone );
+
+ for ( i = 0, l = srcElements.length; i < l; i++ ) {
+ cloneCopyEvent( srcElements[ i ], destElements[ i ] );
+ }
+ } else {
+ cloneCopyEvent( elem, clone );
+ }
+ }
+
+ // Preserve script evaluation history
+ destElements = getAll( clone, "script" );
+ if ( destElements.length > 0 ) {
+ setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
+ }
+
+ // Return the cloned set
+ return clone;
+ },
+
+ cleanData: function( elems ) {
+ var data, elem, type,
+ special = jQuery.event.special,
+ i = 0;
+
+ for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) {
+ if ( acceptData( elem ) ) {
+ if ( ( data = elem[ dataPriv.expando ] ) ) {
+ if ( data.events ) {
+ for ( type in data.events ) {
+ if ( special[ type ] ) {
+ jQuery.event.remove( elem, type );
+
+ // This is a shortcut to avoid jQuery.event.remove's overhead
+ } else {
+ jQuery.removeEvent( elem, type, data.handle );
+ }
+ }
+ }
+
+ // Support: Chrome <=35 - 45+
+ // Assign undefined instead of using delete, see Data#remove
+ elem[ dataPriv.expando ] = undefined;
+ }
+ if ( elem[ dataUser.expando ] ) {
+
+ // Support: Chrome <=35 - 45+
+ // Assign undefined instead of using delete, see Data#remove
+ elem[ dataUser.expando ] = undefined;
+ }
+ }
+ }
+ }
+} );
+
+jQuery.fn.extend( {
+ detach: function( selector ) {
+ return remove( this, selector, true );
+ },
+
+ remove: function( selector ) {
+ return remove( this, selector );
+ },
+
+ text: function( value ) {
+ return access( this, function( value ) {
+ return value === undefined ?
+ jQuery.text( this ) :
+ this.empty().each( function() {
+ if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+ this.textContent = value;
+ }
+ } );
+ }, null, value, arguments.length );
+ },
+
+ append: function() {
+ return domManip( this, arguments, function( elem ) {
+ if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+ var target = manipulationTarget( this, elem );
+ target.appendChild( elem );
+ }
+ } );
+ },
+
+ prepend: function() {
+ return domManip( this, arguments, function( elem ) {
+ if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+ var target = manipulationTarget( this, elem );
+ target.insertBefore( elem, target.firstChild );
+ }
+ } );
+ },
+
+ before: function() {
+ return domManip( this, arguments, function( elem ) {
+ if ( this.parentNode ) {
+ this.parentNode.insertBefore( elem, this );
+ }
+ } );
+ },
+
+ after: function() {
+ return domManip( this, arguments, function( elem ) {
+ if ( this.parentNode ) {
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ }
+ } );
+ },
+
+ empty: function() {
+ var elem,
+ i = 0;
+
+ for ( ; ( elem = this[ i ] ) != null; i++ ) {
+ if ( elem.nodeType === 1 ) {
+
+ // Prevent memory leaks
+ jQuery.cleanData( getAll( elem, false ) );
+
+ // Remove any remaining nodes
+ elem.textContent = "";
+ }
+ }
+
+ return this;
+ },
+
+ clone: function( dataAndEvents, deepDataAndEvents ) {
+ dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+ deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+ return this.map( function() {
+ return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+ } );
+ },
+
+ html: function( value ) {
+ return access( this, function( value ) {
+ var elem = this[ 0 ] || {},
+ i = 0,
+ l = this.length;
+
+ if ( value === undefined && elem.nodeType === 1 ) {
+ return elem.innerHTML;
+ }
+
+ // See if we can take a shortcut and just use innerHTML
+ if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+ !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {
+
+ value = jQuery.htmlPrefilter( value );
+
+ try {
+ for ( ; i < l; i++ ) {
+ elem = this[ i ] || {};
+
+ // Remove element nodes and prevent memory leaks
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( getAll( elem, false ) );
+ elem.innerHTML = value;
+ }
+ }
+
+ elem = 0;
+
+ // If using innerHTML throws an exception, use the fallback method
+ } catch ( e ) {}
+ }
+
+ if ( elem ) {
+ this.empty().append( value );
+ }
+ }, null, value, arguments.length );
+ },
+
+ replaceWith: function() {
+ var ignored = [];
+
+ // Make the changes, replacing each non-ignored context element with the new content
+ return domManip( this, arguments, function( elem ) {
+ var parent = this.parentNode;
+
+ if ( jQuery.inArray( this, ignored ) < 0 ) {
+ jQuery.cleanData( getAll( this ) );
+ if ( parent ) {
+ parent.replaceChild( elem, this );
+ }
+ }
+
+ // Force callback invocation
+ }, ignored );
+ }
+} );
+
+jQuery.each( {
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+}, function( name, original ) {
+ jQuery.fn[ name ] = function( selector ) {
+ var elems,
+ ret = [],
+ insert = jQuery( selector ),
+ last = insert.length - 1,
+ i = 0;
+
+ for ( ; i <= last; i++ ) {
+ elems = i === last ? this : this.clone( true );
+ jQuery( insert[ i ] )[ original ]( elems );
+
+ // Support: Android <=4.0 only, PhantomJS 1 only
+ // .get() because push.apply(_, arraylike) throws on ancient WebKit
+ push.apply( ret, elems.get() );
+ }
+
+ return this.pushStack( ret );
+ };
+} );
+var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
+
+var rcustomProp = /^--/;
+
+
+var getStyles = function( elem ) {
+
+ // Support: IE <=11 only, Firefox <=30 (trac-15098, trac-14150)
+ // IE throws on elements created in popups
+ // FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
+ var view = elem.ownerDocument.defaultView;
+
+ if ( !view || !view.opener ) {
+ view = window;
+ }
+
+ return view.getComputedStyle( elem );
+ };
+
+var swap = function( elem, options, callback ) {
+ var ret, name,
+ old = {};
+
+ // Remember the old values, and insert the new ones
+ for ( name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ ret = callback.call( elem );
+
+ // Revert the old values
+ for ( name in options ) {
+ elem.style[ name ] = old[ name ];
+ }
+
+ return ret;
+};
+
+
+var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
+
+
+
+( function() {
+
+ // Executing both pixelPosition & boxSizingReliable tests require only one layout
+ // so they're executed at the same time to save the second computation.
+ function computeStyleTests() {
+
+ // This is a singleton, we need to execute it only once
+ if ( !div ) {
+ return;
+ }
+
+ container.style.cssText = "position:absolute;left:-11111px;width:60px;" +
+ "margin-top:1px;padding:0;border:0";
+ div.style.cssText =
+ "position:relative;display:block;box-sizing:border-box;overflow:scroll;" +
+ "margin:auto;border:1px;padding:1px;" +
+ "width:60%;top:1%";
+ documentElement.appendChild( container ).appendChild( div );
+
+ var divStyle = window.getComputedStyle( div );
+ pixelPositionVal = divStyle.top !== "1%";
+
+ // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44
+ reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12;
+
+ // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3
+ // Some styles come back with percentage values, even though they shouldn't
+ div.style.right = "60%";
+ pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36;
+
+ // Support: IE 9 - 11 only
+ // Detect misreporting of content dimensions for box-sizing:border-box elements
+ boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36;
+
+ // Support: IE 9 only
+ // Detect overflow:scroll screwiness (gh-3699)
+ // Support: Chrome <=64
+ // Don't get tricked when zoom affects offsetWidth (gh-4029)
+ div.style.position = "absolute";
+ scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12;
+
+ documentElement.removeChild( container );
+
+ // Nullify the div so it wouldn't be stored in the memory and
+ // it will also be a sign that checks already performed
+ div = null;
+ }
+
+ function roundPixelMeasures( measure ) {
+ return Math.round( parseFloat( measure ) );
+ }
+
+ var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal,
+ reliableTrDimensionsVal, reliableMarginLeftVal,
+ container = document.createElement( "div" ),
+ div = document.createElement( "div" );
+
+ // Finish early in limited (non-browser) environments
+ if ( !div.style ) {
+ return;
+ }
+
+ // Support: IE <=9 - 11 only
+ // Style of cloned element affects source element cloned (trac-8908)
+ div.style.backgroundClip = "content-box";
+ div.cloneNode( true ).style.backgroundClip = "";
+ support.clearCloneStyle = div.style.backgroundClip === "content-box";
+
+ jQuery.extend( support, {
+ boxSizingReliable: function() {
+ computeStyleTests();
+ return boxSizingReliableVal;
+ },
+ pixelBoxStyles: function() {
+ computeStyleTests();
+ return pixelBoxStylesVal;
+ },
+ pixelPosition: function() {
+ computeStyleTests();
+ return pixelPositionVal;
+ },
+ reliableMarginLeft: function() {
+ computeStyleTests();
+ return reliableMarginLeftVal;
+ },
+ scrollboxSize: function() {
+ computeStyleTests();
+ return scrollboxSizeVal;
+ },
+
+ // Support: IE 9 - 11+, Edge 15 - 18+
+ // IE/Edge misreport `getComputedStyle` of table rows with width/height
+ // set in CSS while `offset*` properties report correct values.
+ // Behavior in IE 9 is more subtle than in newer versions & it passes
+ // some versions of this test; make sure not to make it pass there!
+ //
+ // Support: Firefox 70+
+ // Only Firefox includes border widths
+ // in computed dimensions. (gh-4529)
+ reliableTrDimensions: function() {
+ var table, tr, trChild, trStyle;
+ if ( reliableTrDimensionsVal == null ) {
+ table = document.createElement( "table" );
+ tr = document.createElement( "tr" );
+ trChild = document.createElement( "div" );
+
+ table.style.cssText = "position:absolute;left:-11111px;border-collapse:separate";
+ tr.style.cssText = "box-sizing:content-box;border:1px solid";
+
+ // Support: Chrome 86+
+ // Height set through cssText does not get applied.
+ // Computed height then comes back as 0.
+ tr.style.height = "1px";
+ trChild.style.height = "9px";
+
+ // Support: Android 8 Chrome 86+
+ // In our bodyBackground.html iframe,
+ // display for all div elements is set to "inline",
+ // which causes a problem only in Android 8 Chrome 86.
+ // Ensuring the div is `display: block`
+ // gets around this issue.
+ trChild.style.display = "block";
+
+ documentElement
+ .appendChild( table )
+ .appendChild( tr )
+ .appendChild( trChild );
+
+ trStyle = window.getComputedStyle( tr );
+ reliableTrDimensionsVal = ( parseInt( trStyle.height, 10 ) +
+ parseInt( trStyle.borderTopWidth, 10 ) +
+ parseInt( trStyle.borderBottomWidth, 10 ) ) === tr.offsetHeight;
+
+ documentElement.removeChild( table );
+ }
+ return reliableTrDimensionsVal;
+ }
+ } );
+} )();
+
+
+function curCSS( elem, name, computed ) {
+ var width, minWidth, maxWidth, ret,
+ isCustomProp = rcustomProp.test( name ),
+
+ // Support: Firefox 51+
+ // Retrieving style before computed somehow
+ // fixes an issue with getting wrong values
+ // on detached elements
+ style = elem.style;
+
+ computed = computed || getStyles( elem );
+
+ // getPropertyValue is needed for:
+ // .css('filter') (IE 9 only, trac-12537)
+ // .css('--customProperty) (gh-3144)
+ if ( computed ) {
+
+ // Support: IE <=9 - 11+
+ // IE only supports `"float"` in `getPropertyValue`; in computed styles
+ // it's only available as `"cssFloat"`. We no longer modify properties
+ // sent to `.css()` apart from camelCasing, so we need to check both.
+ // Normally, this would create difference in behavior: if
+ // `getPropertyValue` returns an empty string, the value returned
+ // by `.css()` would be `undefined`. This is usually the case for
+ // disconnected elements. However, in IE even disconnected elements
+ // with no styles return `"none"` for `getPropertyValue( "float" )`
+ ret = computed.getPropertyValue( name ) || computed[ name ];
+
+ if ( isCustomProp && ret ) {
+
+ // Support: Firefox 105+, Chrome <=105+
+ // Spec requires trimming whitespace for custom properties (gh-4926).
+ // Firefox only trims leading whitespace. Chrome just collapses
+ // both leading & trailing whitespace to a single space.
+ //
+ // Fall back to `undefined` if empty string returned.
+ // This collapses a missing definition with property defined
+ // and set to an empty string but there's no standard API
+ // allowing us to differentiate them without a performance penalty
+ // and returning `undefined` aligns with older jQuery.
+ //
+ // rtrimCSS treats U+000D CARRIAGE RETURN and U+000C FORM FEED
+ // as whitespace while CSS does not, but this is not a problem
+ // because CSS preprocessing replaces them with U+000A LINE FEED
+ // (which *is* CSS whitespace)
+ // https://www.w3.org/TR/css-syntax-3/#input-preprocessing
+ ret = ret.replace( rtrimCSS, "$1" ) || undefined;
+ }
+
+ if ( ret === "" && !isAttached( elem ) ) {
+ ret = jQuery.style( elem, name );
+ }
+
+ // A tribute to the "awesome hack by Dean Edwards"
+ // Android Browser returns percentage for some values,
+ // but width seems to be reliably pixels.
+ // This is against the CSSOM draft spec:
+ // https://drafts.csswg.org/cssom/#resolved-values
+ if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) {
+
+ // Remember the original values
+ width = style.width;
+ minWidth = style.minWidth;
+ maxWidth = style.maxWidth;
+
+ // Put in the new values to get a computed value out
+ style.minWidth = style.maxWidth = style.width = ret;
+ ret = computed.width;
+
+ // Revert the changed values
+ style.width = width;
+ style.minWidth = minWidth;
+ style.maxWidth = maxWidth;
+ }
+ }
+
+ return ret !== undefined ?
+
+ // Support: IE <=9 - 11 only
+ // IE returns zIndex value as an integer.
+ ret + "" :
+ ret;
+}
+
+
+function addGetHookIf( conditionFn, hookFn ) {
+
+ // Define the hook, we'll check on the first run if it's really needed.
+ return {
+ get: function() {
+ if ( conditionFn() ) {
+
+ // Hook not needed (or it's not possible to use it due
+ // to missing dependency), remove it.
+ delete this.get;
+ return;
+ }
+
+ // Hook needed; redefine it so that the support test is not executed again.
+ return ( this.get = hookFn ).apply( this, arguments );
+ }
+ };
+}
+
+
+var cssPrefixes = [ "Webkit", "Moz", "ms" ],
+ emptyStyle = document.createElement( "div" ).style,
+ vendorProps = {};
+
+// Return a vendor-prefixed property or undefined
+function vendorPropName( name ) {
+
+ // Check for vendor prefixed names
+ var capName = name[ 0 ].toUpperCase() + name.slice( 1 ),
+ i = cssPrefixes.length;
+
+ while ( i-- ) {
+ name = cssPrefixes[ i ] + capName;
+ if ( name in emptyStyle ) {
+ return name;
+ }
+ }
+}
+
+// Return a potentially-mapped jQuery.cssProps or vendor prefixed property
+function finalPropName( name ) {
+ var final = jQuery.cssProps[ name ] || vendorProps[ name ];
+
+ if ( final ) {
+ return final;
+ }
+ if ( name in emptyStyle ) {
+ return name;
+ }
+ return vendorProps[ name ] = vendorPropName( name ) || name;
+}
+
+
+var
+
+ // Swappable if display is none or starts with table
+ // except "table", "table-cell", or "table-caption"
+ // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
+ rdisplayswap = /^(none|table(?!-c[ea]).+)/,
+ cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+ cssNormalTransform = {
+ letterSpacing: "0",
+ fontWeight: "400"
+ };
+
+function setPositiveNumber( _elem, value, subtract ) {
+
+ // Any relative (+/-) values have already been
+ // normalized at this point
+ var matches = rcssNum.exec( value );
+ return matches ?
+
+ // Guard against undefined "subtract", e.g., when used as in cssHooks
+ Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) :
+ value;
+}
+
+function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) {
+ var i = dimension === "width" ? 1 : 0,
+ extra = 0,
+ delta = 0,
+ marginDelta = 0;
+
+ // Adjustment may not be necessary
+ if ( box === ( isBorderBox ? "border" : "content" ) ) {
+ return 0;
+ }
+
+ for ( ; i < 4; i += 2 ) {
+
+ // Both box models exclude margin
+ // Count margin delta separately to only add it after scroll gutter adjustment.
+ // This is needed to make negative margins work with `outerHeight( true )` (gh-3982).
+ if ( box === "margin" ) {
+ marginDelta += jQuery.css( elem, box + cssExpand[ i ], true, styles );
+ }
+
+ // If we get here with a content-box, we're seeking "padding" or "border" or "margin"
+ if ( !isBorderBox ) {
+
+ // Add padding
+ delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+
+ // For "border" or "margin", add border
+ if ( box !== "padding" ) {
+ delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+
+ // But still keep track of it otherwise
+ } else {
+ extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+ }
+
+ // If we get here with a border-box (content + padding + border), we're seeking "content" or
+ // "padding" or "margin"
+ } else {
+
+ // For "content", subtract padding
+ if ( box === "content" ) {
+ delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+ }
+
+ // For "content" or "padding", subtract border
+ if ( box !== "margin" ) {
+ delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+ }
+ }
+ }
+
+ // Account for positive content-box scroll gutter when requested by providing computedVal
+ if ( !isBorderBox && computedVal >= 0 ) {
+
+ // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border
+ // Assuming integer scroll gutter, subtract the rest and round down
+ delta += Math.max( 0, Math.ceil(
+ elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
+ computedVal -
+ delta -
+ extra -
+ 0.5
+
+ // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter
+ // Use an explicit zero to avoid NaN (gh-3964)
+ ) ) || 0;
+ }
+
+ return delta + marginDelta;
+}
+
+function getWidthOrHeight( elem, dimension, extra ) {
+
+ // Start with computed style
+ var styles = getStyles( elem ),
+
+ // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322).
+ // Fake content-box until we know it's needed to know the true value.
+ boxSizingNeeded = !support.boxSizingReliable() || extra,
+ isBorderBox = boxSizingNeeded &&
+ jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
+ valueIsBorderBox = isBorderBox,
+
+ val = curCSS( elem, dimension, styles ),
+ offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 );
+
+ // Support: Firefox <=54
+ // Return a confounding non-pixel value or feign ignorance, as appropriate.
+ if ( rnumnonpx.test( val ) ) {
+ if ( !extra ) {
+ return val;
+ }
+ val = "auto";
+ }
+
+
+ // Support: IE 9 - 11 only
+ // Use offsetWidth/offsetHeight for when box sizing is unreliable.
+ // In those cases, the computed value can be trusted to be border-box.
+ if ( ( !support.boxSizingReliable() && isBorderBox ||
+
+ // Support: IE 10 - 11+, Edge 15 - 18+
+ // IE/Edge misreport `getComputedStyle` of table rows with width/height
+ // set in CSS while `offset*` properties report correct values.
+ // Interestingly, in some cases IE 9 doesn't suffer from this issue.
+ !support.reliableTrDimensions() && nodeName( elem, "tr" ) ||
+
+ // Fall back to offsetWidth/offsetHeight when value is "auto"
+ // This happens for inline elements with no explicit setting (gh-3571)
+ val === "auto" ||
+
+ // Support: Android <=4.1 - 4.3 only
+ // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602)
+ !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) &&
+
+ // Make sure the element is visible & connected
+ elem.getClientRects().length ) {
+
+ isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
+
+ // Where available, offsetWidth/offsetHeight approximate border box dimensions.
+ // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the
+ // retrieved value as a content box dimension.
+ valueIsBorderBox = offsetProp in elem;
+ if ( valueIsBorderBox ) {
+ val = elem[ offsetProp ];
+ }
+ }
+
+ // Normalize "" and auto
+ val = parseFloat( val ) || 0;
+
+ // Adjust for the element's box model
+ return ( val +
+ boxModelAdjustment(
+ elem,
+ dimension,
+ extra || ( isBorderBox ? "border" : "content" ),
+ valueIsBorderBox,
+ styles,
+
+ // Provide the current computed size to request scroll gutter calculation (gh-3589)
+ val
+ )
+ ) + "px";
+}
+
+jQuery.extend( {
+
+ // Add in style property hooks for overriding the default
+ // behavior of getting and setting a style property
+ cssHooks: {
+ opacity: {
+ get: function( elem, computed ) {
+ if ( computed ) {
+
+ // We should always get a number back from opacity
+ var ret = curCSS( elem, "opacity" );
+ return ret === "" ? "1" : ret;
+ }
+ }
+ }
+ },
+
+ // Don't automatically add "px" to these possibly-unitless properties
+ cssNumber: {
+ animationIterationCount: true,
+ aspectRatio: true,
+ borderImageSlice: true,
+ columnCount: true,
+ flexGrow: true,
+ flexShrink: true,
+ fontWeight: true,
+ gridArea: true,
+ gridColumn: true,
+ gridColumnEnd: true,
+ gridColumnStart: true,
+ gridRow: true,
+ gridRowEnd: true,
+ gridRowStart: true,
+ lineHeight: true,
+ opacity: true,
+ order: true,
+ orphans: true,
+ scale: true,
+ widows: true,
+ zIndex: true,
+ zoom: true,
+
+ // SVG-related
+ fillOpacity: true,
+ floodOpacity: true,
+ stopOpacity: true,
+ strokeMiterlimit: true,
+ strokeOpacity: true
+ },
+
+ // Add in properties whose names you wish to fix before
+ // setting or getting the value
+ cssProps: {},
+
+ // Get and set the style property on a DOM Node
+ style: function( elem, name, value, extra ) {
+
+ // Don't set styles on text and comment nodes
+ if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+ return;
+ }
+
+ // Make sure that we're working with the right name
+ var ret, type, hooks,
+ origName = camelCase( name ),
+ isCustomProp = rcustomProp.test( name ),
+ style = elem.style;
+
+ // Make sure that we're working with the right name. We don't
+ // want to query the value if it is a CSS custom property
+ // since they are user-defined.
+ if ( !isCustomProp ) {
+ name = finalPropName( origName );
+ }
+
+ // Gets hook for the prefixed version, then unprefixed version
+ hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+ // Check if we're setting a value
+ if ( value !== undefined ) {
+ type = typeof value;
+
+ // Convert "+=" or "-=" to relative numbers (trac-7345)
+ if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
+ value = adjustCSS( elem, name, ret );
+
+ // Fixes bug trac-9237
+ type = "number";
+ }
+
+ // Make sure that null and NaN values aren't set (trac-7116)
+ if ( value == null || value !== value ) {
+ return;
+ }
+
+ // If a number was passed in, add the unit (except for certain CSS properties)
+ // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append
+ // "px" to a few hardcoded values.
+ if ( type === "number" && !isCustomProp ) {
+ value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" );
+ }
+
+ // background-* props affect original clone's values
+ if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
+ style[ name ] = "inherit";
+ }
+
+ // If a hook was provided, use that value, otherwise just set the specified value
+ if ( !hooks || !( "set" in hooks ) ||
+ ( value = hooks.set( elem, value, extra ) ) !== undefined ) {
+
+ if ( isCustomProp ) {
+ style.setProperty( name, value );
+ } else {
+ style[ name ] = value;
+ }
+ }
+
+ } else {
+
+ // If a hook was provided get the non-computed value from there
+ if ( hooks && "get" in hooks &&
+ ( ret = hooks.get( elem, false, extra ) ) !== undefined ) {
+
+ return ret;
+ }
+
+ // Otherwise just get the value from the style object
+ return style[ name ];
+ }
+ },
+
+ css: function( elem, name, extra, styles ) {
+ var val, num, hooks,
+ origName = camelCase( name ),
+ isCustomProp = rcustomProp.test( name );
+
+ // Make sure that we're working with the right name. We don't
+ // want to modify the value if it is a CSS custom property
+ // since they are user-defined.
+ if ( !isCustomProp ) {
+ name = finalPropName( origName );
+ }
+
+ // Try prefixed name followed by the unprefixed name
+ hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+ // If a hook was provided get the computed value from there
+ if ( hooks && "get" in hooks ) {
+ val = hooks.get( elem, true, extra );
+ }
+
+ // Otherwise, if a way to get the computed value exists, use that
+ if ( val === undefined ) {
+ val = curCSS( elem, name, styles );
+ }
+
+ // Convert "normal" to computed value
+ if ( val === "normal" && name in cssNormalTransform ) {
+ val = cssNormalTransform[ name ];
+ }
+
+ // Make numeric if forced or a qualifier was provided and val looks numeric
+ if ( extra === "" || extra ) {
+ num = parseFloat( val );
+ return extra === true || isFinite( num ) ? num || 0 : val;
+ }
+
+ return val;
+ }
+} );
+
+jQuery.each( [ "height", "width" ], function( _i, dimension ) {
+ jQuery.cssHooks[ dimension ] = {
+ get: function( elem, computed, extra ) {
+ if ( computed ) {
+
+ // Certain elements can have dimension info if we invisibly show them
+ // but it must have a current display style that would benefit
+ return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&
+
+ // Support: Safari 8+
+ // Table columns in Safari have non-zero offsetWidth & zero
+ // getBoundingClientRect().width unless display is changed.
+ // Support: IE <=11 only
+ // Running getBoundingClientRect on a disconnected node
+ // in IE throws an error.
+ ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
+ swap( elem, cssShow, function() {
+ return getWidthOrHeight( elem, dimension, extra );
+ } ) :
+ getWidthOrHeight( elem, dimension, extra );
+ }
+ },
+
+ set: function( elem, value, extra ) {
+ var matches,
+ styles = getStyles( elem ),
+
+ // Only read styles.position if the test has a chance to fail
+ // to avoid forcing a reflow.
+ scrollboxSizeBuggy = !support.scrollboxSize() &&
+ styles.position === "absolute",
+
+ // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991)
+ boxSizingNeeded = scrollboxSizeBuggy || extra,
+ isBorderBox = boxSizingNeeded &&
+ jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
+ subtract = extra ?
+ boxModelAdjustment(
+ elem,
+ dimension,
+ extra,
+ isBorderBox,
+ styles
+ ) :
+ 0;
+
+ // Account for unreliable border-box dimensions by comparing offset* to computed and
+ // faking a content-box to get border and padding (gh-3699)
+ if ( isBorderBox && scrollboxSizeBuggy ) {
+ subtract -= Math.ceil(
+ elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
+ parseFloat( styles[ dimension ] ) -
+ boxModelAdjustment( elem, dimension, "border", false, styles ) -
+ 0.5
+ );
+ }
+
+ // Convert to pixels if value adjustment is needed
+ if ( subtract && ( matches = rcssNum.exec( value ) ) &&
+ ( matches[ 3 ] || "px" ) !== "px" ) {
+
+ elem.style[ dimension ] = value;
+ value = jQuery.css( elem, dimension );
+ }
+
+ return setPositiveNumber( elem, value, subtract );
+ }
+ };
+} );
+
+jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,
+ function( elem, computed ) {
+ if ( computed ) {
+ return ( parseFloat( curCSS( elem, "marginLeft" ) ) ||
+ elem.getBoundingClientRect().left -
+ swap( elem, { marginLeft: 0 }, function() {
+ return elem.getBoundingClientRect().left;
+ } )
+ ) + "px";
+ }
+ }
+);
+
+// These hooks are used by animate to expand properties
+jQuery.each( {
+ margin: "",
+ padding: "",
+ border: "Width"
+}, function( prefix, suffix ) {
+ jQuery.cssHooks[ prefix + suffix ] = {
+ expand: function( value ) {
+ var i = 0,
+ expanded = {},
+
+ // Assumes a single number if not a string
+ parts = typeof value === "string" ? value.split( " " ) : [ value ];
+
+ for ( ; i < 4; i++ ) {
+ expanded[ prefix + cssExpand[ i ] + suffix ] =
+ parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+ }
+
+ return expanded;
+ }
+ };
+
+ if ( prefix !== "margin" ) {
+ jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
+ }
+} );
+
+jQuery.fn.extend( {
+ css: function( name, value ) {
+ return access( this, function( elem, name, value ) {
+ var styles, len,
+ map = {},
+ i = 0;
+
+ if ( Array.isArray( name ) ) {
+ styles = getStyles( elem );
+ len = name.length;
+
+ for ( ; i < len; i++ ) {
+ map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
+ }
+
+ return map;
+ }
+
+ return value !== undefined ?
+ jQuery.style( elem, name, value ) :
+ jQuery.css( elem, name );
+ }, name, value, arguments.length > 1 );
+ }
+} );
+
+
+// Based off of the plugin by Clint Helfers, with permission.
+jQuery.fn.delay = function( time, type ) {
+ time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+ type = type || "fx";
+
+ return this.queue( type, function( next, hooks ) {
+ var timeout = window.setTimeout( next, time );
+ hooks.stop = function() {
+ window.clearTimeout( timeout );
+ };
+ } );
+};
+
+
+( function() {
+ var input = document.createElement( "input" ),
+ select = document.createElement( "select" ),
+ opt = select.appendChild( document.createElement( "option" ) );
+
+ input.type = "checkbox";
+
+ // Support: Android <=4.3 only
+ // Default value for a checkbox should be "on"
+ support.checkOn = input.value !== "";
+
+ // Support: IE <=11 only
+ // Must access selectedIndex to make default options select
+ support.optSelected = opt.selected;
+
+ // Support: IE <=11 only
+ // An input loses its value after becoming a radio
+ input = document.createElement( "input" );
+ input.value = "t";
+ input.type = "radio";
+ support.radioValue = input.value === "t";
+} )();
+
+
+var boolHook,
+ attrHandle = jQuery.expr.attrHandle;
+
+jQuery.fn.extend( {
+ attr: function( name, value ) {
+ return access( this, jQuery.attr, name, value, arguments.length > 1 );
+ },
+
+ removeAttr: function( name ) {
+ return this.each( function() {
+ jQuery.removeAttr( this, name );
+ } );
+ }
+} );
+
+jQuery.extend( {
+ attr: function( elem, name, value ) {
+ var ret, hooks,
+ nType = elem.nodeType;
+
+ // Don't get/set attributes on text, comment and attribute nodes
+ if ( nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ // Fallback to prop when attributes are not supported
+ if ( typeof elem.getAttribute === "undefined" ) {
+ return jQuery.prop( elem, name, value );
+ }
+
+ // Attribute hooks are determined by the lowercase version
+ // Grab necessary hook if one is defined
+ if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
+ hooks = jQuery.attrHooks[ name.toLowerCase() ] ||
+ ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined );
+ }
+
+ if ( value !== undefined ) {
+ if ( value === null ) {
+ jQuery.removeAttr( elem, name );
+ return;
+ }
+
+ if ( hooks && "set" in hooks &&
+ ( ret = hooks.set( elem, value, name ) ) !== undefined ) {
+ return ret;
+ }
+
+ elem.setAttribute( name, value + "" );
+ return value;
+ }
+
+ if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
+ return ret;
+ }
+
+ ret = jQuery.find.attr( elem, name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return ret == null ? undefined : ret;
+ },
+
+ attrHooks: {
+ type: {
+ set: function( elem, value ) {
+ if ( !support.radioValue && value === "radio" &&
+ nodeName( elem, "input" ) ) {
+ var val = elem.value;
+ elem.setAttribute( "type", value );
+ if ( val ) {
+ elem.value = val;
+ }
+ return value;
+ }
+ }
+ }
+ },
+
+ removeAttr: function( elem, value ) {
+ var name,
+ i = 0,
+
+ // Attribute names can contain non-HTML whitespace characters
+ // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
+ attrNames = value && value.match( rnothtmlwhite );
+
+ if ( attrNames && elem.nodeType === 1 ) {
+ while ( ( name = attrNames[ i++ ] ) ) {
+ elem.removeAttribute( name );
+ }
+ }
+ }
+} );
+
+// Hooks for boolean attributes
+boolHook = {
+ set: function( elem, value, name ) {
+ if ( value === false ) {
+
+ // Remove boolean attributes when set to false
+ jQuery.removeAttr( elem, name );
+ } else {
+ elem.setAttribute( name, name );
+ }
+ return name;
+ }
+};
+
+jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) {
+ var getter = attrHandle[ name ] || jQuery.find.attr;
+
+ attrHandle[ name ] = function( elem, name, isXML ) {
+ var ret, handle,
+ lowercaseName = name.toLowerCase();
+
+ if ( !isXML ) {
+
+ // Avoid an infinite loop by temporarily removing this function from the getter
+ handle = attrHandle[ lowercaseName ];
+ attrHandle[ lowercaseName ] = ret;
+ ret = getter( elem, name, isXML ) != null ?
+ lowercaseName :
+ null;
+ attrHandle[ lowercaseName ] = handle;
+ }
+ return ret;
+ };
+} );
+
+
+
+
+var rfocusable = /^(?:input|select|textarea|button)$/i,
+ rclickable = /^(?:a|area)$/i;
+
+jQuery.fn.extend( {
+ prop: function( name, value ) {
+ return access( this, jQuery.prop, name, value, arguments.length > 1 );
+ },
+
+ removeProp: function( name ) {
+ return this.each( function() {
+ delete this[ jQuery.propFix[ name ] || name ];
+ } );
+ }
+} );
+
+jQuery.extend( {
+ prop: function( elem, name, value ) {
+ var ret, hooks,
+ nType = elem.nodeType;
+
+ // Don't get/set properties on text, comment and attribute nodes
+ if ( nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
+
+ // Fix name and attach hooks
+ name = jQuery.propFix[ name ] || name;
+ hooks = jQuery.propHooks[ name ];
+ }
+
+ if ( value !== undefined ) {
+ if ( hooks && "set" in hooks &&
+ ( ret = hooks.set( elem, value, name ) ) !== undefined ) {
+ return ret;
+ }
+
+ return ( elem[ name ] = value );
+ }
+
+ if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
+ return ret;
+ }
+
+ return elem[ name ];
+ },
+
+ propHooks: {
+ tabIndex: {
+ get: function( elem ) {
+
+ // Support: IE <=9 - 11 only
+ // elem.tabIndex doesn't always return the
+ // correct value when it hasn't been explicitly set
+ // Use proper attribute retrieval (trac-12072)
+ var tabindex = jQuery.find.attr( elem, "tabindex" );
+
+ if ( tabindex ) {
+ return parseInt( tabindex, 10 );
+ }
+
+ if (
+ rfocusable.test( elem.nodeName ) ||
+ rclickable.test( elem.nodeName ) &&
+ elem.href
+ ) {
+ return 0;
+ }
+
+ return -1;
+ }
+ }
+ },
+
+ propFix: {
+ "for": "htmlFor",
+ "class": "className"
+ }
+} );
+
+// Support: IE <=11 only
+// Accessing the selectedIndex property
+// forces the browser to respect setting selected
+// on the option
+// The getter ensures a default option is selected
+// when in an optgroup
+// eslint rule "no-unused-expressions" is disabled for this code
+// since it considers such accessions noop
+if ( !support.optSelected ) {
+ jQuery.propHooks.selected = {
+ get: function( elem ) {
+
+ /* eslint no-unused-expressions: "off" */
+
+ var parent = elem.parentNode;
+ if ( parent && parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ return null;
+ },
+ set: function( elem ) {
+
+ /* eslint no-unused-expressions: "off" */
+
+ var parent = elem.parentNode;
+ if ( parent ) {
+ parent.selectedIndex;
+
+ if ( parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ }
+ }
+ };
+}
+
+jQuery.each( [
+ "tabIndex",
+ "readOnly",
+ "maxLength",
+ "cellSpacing",
+ "cellPadding",
+ "rowSpan",
+ "colSpan",
+ "useMap",
+ "frameBorder",
+ "contentEditable"
+], function() {
+ jQuery.propFix[ this.toLowerCase() ] = this;
+} );
+
+
+
+
+ // Strip and collapse whitespace according to HTML spec
+ // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace
+ function stripAndCollapse( value ) {
+ var tokens = value.match( rnothtmlwhite ) || [];
+ return tokens.join( " " );
+ }
+
+
+function getClass( elem ) {
+ return elem.getAttribute && elem.getAttribute( "class" ) || "";
+}
+
+function classesToArray( value ) {
+ if ( Array.isArray( value ) ) {
+ return value;
+ }
+ if ( typeof value === "string" ) {
+ return value.match( rnothtmlwhite ) || [];
+ }
+ return [];
+}
+
+jQuery.fn.extend( {
+ addClass: function( value ) {
+ var classNames, cur, curValue, className, i, finalValue;
+
+ if ( isFunction( value ) ) {
+ return this.each( function( j ) {
+ jQuery( this ).addClass( value.call( this, j, getClass( this ) ) );
+ } );
+ }
+
+ classNames = classesToArray( value );
+
+ if ( classNames.length ) {
+ return this.each( function() {
+ curValue = getClass( this );
+ cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
+
+ if ( cur ) {
+ for ( i = 0; i < classNames.length; i++ ) {
+ className = classNames[ i ];
+ if ( cur.indexOf( " " + className + " " ) < 0 ) {
+ cur += className + " ";
+ }
+ }
+
+ // Only assign if different to avoid unneeded rendering.
+ finalValue = stripAndCollapse( cur );
+ if ( curValue !== finalValue ) {
+ this.setAttribute( "class", finalValue );
+ }
+ }
+ } );
+ }
+
+ return this;
+ },
+
+ removeClass: function( value ) {
+ var classNames, cur, curValue, className, i, finalValue;
+
+ if ( isFunction( value ) ) {
+ return this.each( function( j ) {
+ jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) );
+ } );
+ }
+
+ if ( !arguments.length ) {
+ return this.attr( "class", "" );
+ }
+
+ classNames = classesToArray( value );
+
+ if ( classNames.length ) {
+ return this.each( function() {
+ curValue = getClass( this );
+
+ // This expression is here for better compressibility (see addClass)
+ cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
+
+ if ( cur ) {
+ for ( i = 0; i < classNames.length; i++ ) {
+ className = classNames[ i ];
+
+ // Remove *all* instances
+ while ( cur.indexOf( " " + className + " " ) > -1 ) {
+ cur = cur.replace( " " + className + " ", " " );
+ }
+ }
+
+ // Only assign if different to avoid unneeded rendering.
+ finalValue = stripAndCollapse( cur );
+ if ( curValue !== finalValue ) {
+ this.setAttribute( "class", finalValue );
+ }
+ }
+ } );
+ }
+
+ return this;
+ },
+
+ toggleClass: function( value, stateVal ) {
+ var classNames, className, i, self,
+ type = typeof value,
+ isValidValue = type === "string" || Array.isArray( value );
+
+ if ( isFunction( value ) ) {
+ return this.each( function( i ) {
+ jQuery( this ).toggleClass(
+ value.call( this, i, getClass( this ), stateVal ),
+ stateVal
+ );
+ } );
+ }
+
+ if ( typeof stateVal === "boolean" && isValidValue ) {
+ return stateVal ? this.addClass( value ) : this.removeClass( value );
+ }
+
+ classNames = classesToArray( value );
+
+ return this.each( function() {
+ if ( isValidValue ) {
+
+ // Toggle individual class names
+ self = jQuery( this );
+
+ for ( i = 0; i < classNames.length; i++ ) {
+ className = classNames[ i ];
+
+ // Check each className given, space separated list
+ if ( self.hasClass( className ) ) {
+ self.removeClass( className );
+ } else {
+ self.addClass( className );
+ }
+ }
+
+ // Toggle whole class name
+ } else if ( value === undefined || type === "boolean" ) {
+ className = getClass( this );
+ if ( className ) {
+
+ // Store className if set
+ dataPriv.set( this, "__className__", className );
+ }
+
+ // If the element has a class name or if we're passed `false`,
+ // then remove the whole classname (if there was one, the above saved it).
+ // Otherwise bring back whatever was previously saved (if anything),
+ // falling back to the empty string if nothing was stored.
+ if ( this.setAttribute ) {
+ this.setAttribute( "class",
+ className || value === false ?
+ "" :
+ dataPriv.get( this, "__className__" ) || ""
+ );
+ }
+ }
+ } );
+ },
+
+ hasClass: function( selector ) {
+ var className, elem,
+ i = 0;
+
+ className = " " + selector + " ";
+ while ( ( elem = this[ i++ ] ) ) {
+ if ( elem.nodeType === 1 &&
+ ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+} );
+
+
+
+
+var rreturn = /\r/g;
+
+jQuery.fn.extend( {
+ val: function( value ) {
+ var hooks, ret, valueIsFunction,
+ elem = this[ 0 ];
+
+ if ( !arguments.length ) {
+ if ( elem ) {
+ hooks = jQuery.valHooks[ elem.type ] ||
+ jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+ if ( hooks &&
+ "get" in hooks &&
+ ( ret = hooks.get( elem, "value" ) ) !== undefined
+ ) {
+ return ret;
+ }
+
+ ret = elem.value;
+
+ // Handle most common string cases
+ if ( typeof ret === "string" ) {
+ return ret.replace( rreturn, "" );
+ }
+
+ // Handle cases where value is null/undef or number
+ return ret == null ? "" : ret;
+ }
+
+ return;
+ }
+
+ valueIsFunction = isFunction( value );
+
+ return this.each( function( i ) {
+ var val;
+
+ if ( this.nodeType !== 1 ) {
+ return;
+ }
+
+ if ( valueIsFunction ) {
+ val = value.call( this, i, jQuery( this ).val() );
+ } else {
+ val = value;
+ }
+
+ // Treat null/undefined as ""; convert numbers to string
+ if ( val == null ) {
+ val = "";
+
+ } else if ( typeof val === "number" ) {
+ val += "";
+
+ } else if ( Array.isArray( val ) ) {
+ val = jQuery.map( val, function( value ) {
+ return value == null ? "" : value + "";
+ } );
+ }
+
+ hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+ // If set returns undefined, fall back to normal setting
+ if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) {
+ this.value = val;
+ }
+ } );
+ }
+} );
+
+jQuery.extend( {
+ valHooks: {
+ option: {
+ get: function( elem ) {
+
+ var val = jQuery.find.attr( elem, "value" );
+ return val != null ?
+ val :
+
+ // Support: IE <=10 - 11 only
+ // option.text throws exceptions (trac-14686, trac-14858)
+ // Strip and collapse whitespace
+ // https://html.spec.whatwg.org/#strip-and-collapse-whitespace
+ stripAndCollapse( jQuery.text( elem ) );
+ }
+ },
+ select: {
+ get: function( elem ) {
+ var value, option, i,
+ options = elem.options,
+ index = elem.selectedIndex,
+ one = elem.type === "select-one",
+ values = one ? null : [],
+ max = one ? index + 1 : options.length;
+
+ if ( index < 0 ) {
+ i = max;
+
+ } else {
+ i = one ? index : 0;
+ }
+
+ // Loop through all the selected options
+ for ( ; i < max; i++ ) {
+ option = options[ i ];
+
+ // Support: IE <=9 only
+ // IE8-9 doesn't update selected after form reset (trac-2551)
+ if ( ( option.selected || i === index ) &&
+
+ // Don't return options that are disabled or in a disabled optgroup
+ !option.disabled &&
+ ( !option.parentNode.disabled ||
+ !nodeName( option.parentNode, "optgroup" ) ) ) {
+
+ // Get the specific value for the option
+ value = jQuery( option ).val();
+
+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ return values;
+ },
+
+ set: function( elem, value ) {
+ var optionSet, option,
+ options = elem.options,
+ values = jQuery.makeArray( value ),
+ i = options.length;
+
+ while ( i-- ) {
+ option = options[ i ];
+
+ /* eslint-disable no-cond-assign */
+
+ if ( option.selected =
+ jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1
+ ) {
+ optionSet = true;
+ }
+
+ /* eslint-enable no-cond-assign */
+ }
+
+ // Force browsers to behave consistently when non-matching value is set
+ if ( !optionSet ) {
+ elem.selectedIndex = -1;
+ }
+ return values;
+ }
+ }
+ }
+} );
+
+// Radios and checkboxes getter/setter
+jQuery.each( [ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = {
+ set: function( elem, value ) {
+ if ( Array.isArray( value ) ) {
+ return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 );
+ }
+ }
+ };
+ if ( !support.checkOn ) {
+ jQuery.valHooks[ this ].get = function( elem ) {
+ return elem.getAttribute( "value" ) === null ? "on" : elem.value;
+ };
+ }
+} );
+
+
+
+
+// Return jQuery for attributes-only inclusion
+
+
+// Cross-browser xml parsing
+jQuery.parseXML = function( data ) {
+ var xml, parserErrorElem;
+ if ( !data || typeof data !== "string" ) {
+ return null;
+ }
+
+ // Support: IE 9 - 11 only
+ // IE throws on parseFromString with invalid input.
+ try {
+ xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" );
+ } catch ( e ) {}
+
+ parserErrorElem = xml && xml.getElementsByTagName( "parsererror" )[ 0 ];
+ if ( !xml || parserErrorElem ) {
+ jQuery.error( "Invalid XML: " + (
+ parserErrorElem ?
+ jQuery.map( parserErrorElem.childNodes, function( el ) {
+ return el.textContent;
+ } ).join( "\n" ) :
+ data
+ ) );
+ }
+ return xml;
+};
+
+
+var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+ stopPropagationCallback = function( e ) {
+ e.stopPropagation();
+ };
+
+jQuery.extend( jQuery.event, {
+
+ trigger: function( event, data, elem, onlyHandlers ) {
+
+ var i, cur, tmp, bubbleType, ontype, handle, special, lastElement,
+ eventPath = [ elem || document ],
+ type = hasOwn.call( event, "type" ) ? event.type : event,
+ namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : [];
+
+ cur = lastElement = tmp = elem = elem || document;
+
+ // Don't do events on text and comment nodes
+ if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return;
+ }
+
+ // focus/blur morphs to focusin/out; ensure we're not firing them right now
+ if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+ return;
+ }
+
+ if ( type.indexOf( "." ) > -1 ) {
+
+ // Namespaced trigger; create a regexp to match event type in handle()
+ namespaces = type.split( "." );
+ type = namespaces.shift();
+ namespaces.sort();
+ }
+ ontype = type.indexOf( ":" ) < 0 && "on" + type;
+
+ // Caller can pass in a jQuery.Event object, Object, or just an event type string
+ event = event[ jQuery.expando ] ?
+ event :
+ new jQuery.Event( type, typeof event === "object" && event );
+
+ // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
+ event.isTrigger = onlyHandlers ? 2 : 3;
+ event.namespace = namespaces.join( "." );
+ event.rnamespace = event.namespace ?
+ new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) :
+ null;
+
+ // Clean up the event in case it is being reused
+ event.result = undefined;
+ if ( !event.target ) {
+ event.target = elem;
+ }
+
+ // Clone any incoming data and prepend the event, creating the handler arg list
+ data = data == null ?
+ [ event ] :
+ jQuery.makeArray( data, [ event ] );
+
+ // Allow special events to draw outside the lines
+ special = jQuery.event.special[ type ] || {};
+ if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
+ return;
+ }
+
+ // Determine event propagation path in advance, per W3C events spec (trac-9951)
+ // Bubble up to document, then to window; watch for a global ownerDocument var (trac-9724)
+ if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) {
+
+ bubbleType = special.delegateType || type;
+ if ( !rfocusMorph.test( bubbleType + type ) ) {
+ cur = cur.parentNode;
+ }
+ for ( ; cur; cur = cur.parentNode ) {
+ eventPath.push( cur );
+ tmp = cur;
+ }
+
+ // Only add window if we got to document (e.g., not plain obj or detached DOM)
+ if ( tmp === ( elem.ownerDocument || document ) ) {
+ eventPath.push( tmp.defaultView || tmp.parentWindow || window );
+ }
+ }
+
+ // Fire handlers on the event path
+ i = 0;
+ while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {
+ lastElement = cur;
+ event.type = i > 1 ?
+ bubbleType :
+ special.bindType || type;
+
+ // jQuery handler
+ handle = ( dataPriv.get( cur, "events" ) || Object.create( null ) )[ event.type ] &&
+ dataPriv.get( cur, "handle" );
+ if ( handle ) {
+ handle.apply( cur, data );
+ }
+
+ // Native handler
+ handle = ontype && cur[ ontype ];
+ if ( handle && handle.apply && acceptData( cur ) ) {
+ event.result = handle.apply( cur, data );
+ if ( event.result === false ) {
+ event.preventDefault();
+ }
+ }
+ }
+ event.type = type;
+
+ // If nobody prevented the default action, do it now
+ if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+ if ( ( !special._default ||
+ special._default.apply( eventPath.pop(), data ) === false ) &&
+ acceptData( elem ) ) {
+
+ // Call a native DOM method on the target with the same name as the event.
+ // Don't do default actions on window, that's where global variables be (trac-6170)
+ if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) {
+
+ // Don't re-trigger an onFOO event when we call its FOO() method
+ tmp = elem[ ontype ];
+
+ if ( tmp ) {
+ elem[ ontype ] = null;
+ }
+
+ // Prevent re-triggering of the same event, since we already bubbled it above
+ jQuery.event.triggered = type;
+
+ if ( event.isPropagationStopped() ) {
+ lastElement.addEventListener( type, stopPropagationCallback );
+ }
+
+ elem[ type ]();
+
+ if ( event.isPropagationStopped() ) {
+ lastElement.removeEventListener( type, stopPropagationCallback );
+ }
+
+ jQuery.event.triggered = undefined;
+
+ if ( tmp ) {
+ elem[ ontype ] = tmp;
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ // Piggyback on a donor event to simulate a different one
+ // Used only for `focus(in | out)` events
+ simulate: function( type, elem, event ) {
+ var e = jQuery.extend(
+ new jQuery.Event(),
+ event,
+ {
+ type: type,
+ isSimulated: true
+ }
+ );
+
+ jQuery.event.trigger( e, null, elem );
+ }
+
+} );
+
+jQuery.fn.extend( {
+
+ trigger: function( type, data ) {
+ return this.each( function() {
+ jQuery.event.trigger( type, data, this );
+ } );
+ },
+ triggerHandler: function( type, data ) {
+ var elem = this[ 0 ];
+ if ( elem ) {
+ return jQuery.event.trigger( type, data, elem, true );
+ }
+ }
+} );
+
+
+var
+ rbracket = /\[\]$/,
+ rCRLF = /\r?\n/g,
+ rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
+ rsubmittable = /^(?:input|select|textarea|keygen)/i;
+
+function buildParams( prefix, obj, traditional, add ) {
+ var name;
+
+ if ( Array.isArray( obj ) ) {
+
+ // Serialize array item.
+ jQuery.each( obj, function( i, v ) {
+ if ( traditional || rbracket.test( prefix ) ) {
+
+ // Treat each array item as a scalar.
+ add( prefix, v );
+
+ } else {
+
+ // Item is non-scalar (array or object), encode its numeric index.
+ buildParams(
+ prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]",
+ v,
+ traditional,
+ add
+ );
+ }
+ } );
+
+ } else if ( !traditional && toType( obj ) === "object" ) {
+
+ // Serialize object item.
+ for ( name in obj ) {
+ buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+ }
+
+ } else {
+
+ // Serialize scalar item.
+ add( prefix, obj );
+ }
+}
+
+// Serialize an array of form elements or a set of
+// key/values into a query string
+jQuery.param = function( a, traditional ) {
+ var prefix,
+ s = [],
+ add = function( key, valueOrFunction ) {
+
+ // If value is a function, invoke it and use its return value
+ var value = isFunction( valueOrFunction ) ?
+ valueOrFunction() :
+ valueOrFunction;
+
+ s[ s.length ] = encodeURIComponent( key ) + "=" +
+ encodeURIComponent( value == null ? "" : value );
+ };
+
+ if ( a == null ) {
+ return "";
+ }
+
+ // If an array was passed in, assume that it is an array of form elements.
+ if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+
+ // Serialize the form elements
+ jQuery.each( a, function() {
+ add( this.name, this.value );
+ } );
+
+ } else {
+
+ // If traditional, encode the "old" way (the way 1.3.2 or older
+ // did it), otherwise encode params recursively.
+ for ( prefix in a ) {
+ buildParams( prefix, a[ prefix ], traditional, add );
+ }
+ }
+
+ // Return the resulting serialization
+ return s.join( "&" );
+};
+
+jQuery.fn.extend( {
+ serialize: function() {
+ return jQuery.param( this.serializeArray() );
+ },
+ serializeArray: function() {
+ return this.map( function() {
+
+ // Can add propHook for "elements" to filter or add form elements
+ var elements = jQuery.prop( this, "elements" );
+ return elements ? jQuery.makeArray( elements ) : this;
+ } ).filter( function() {
+ var type = this.type;
+
+ // Use .is( ":disabled" ) so that fieldset[disabled] works
+ return this.name && !jQuery( this ).is( ":disabled" ) &&
+ rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
+ ( this.checked || !rcheckableType.test( type ) );
+ } ).map( function( _i, elem ) {
+ var val = jQuery( this ).val();
+
+ if ( val == null ) {
+ return null;
+ }
+
+ if ( Array.isArray( val ) ) {
+ return jQuery.map( val, function( val ) {
+ return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ } );
+ }
+
+ return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ } ).get();
+ }
+} );
+
+
+jQuery.fn.extend( {
+ wrapAll: function( html ) {
+ var wrap;
+
+ if ( this[ 0 ] ) {
+ if ( isFunction( html ) ) {
+ html = html.call( this[ 0 ] );
+ }
+
+ // The elements to wrap the target around
+ wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );
+
+ if ( this[ 0 ].parentNode ) {
+ wrap.insertBefore( this[ 0 ] );
+ }
+
+ wrap.map( function() {
+ var elem = this;
+
+ while ( elem.firstElementChild ) {
+ elem = elem.firstElementChild;
+ }
+
+ return elem;
+ } ).append( this );
+ }
+
+ return this;
+ },
+
+ wrapInner: function( html ) {
+ if ( isFunction( html ) ) {
+ return this.each( function( i ) {
+ jQuery( this ).wrapInner( html.call( this, i ) );
+ } );
+ }
+
+ return this.each( function() {
+ var self = jQuery( this ),
+ contents = self.contents();
+
+ if ( contents.length ) {
+ contents.wrapAll( html );
+
+ } else {
+ self.append( html );
+ }
+ } );
+ },
+
+ wrap: function( html ) {
+ var htmlIsFunction = isFunction( html );
+
+ return this.each( function( i ) {
+ jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html );
+ } );
+ },
+
+ unwrap: function( selector ) {
+ this.parent( selector ).not( "body" ).each( function() {
+ jQuery( this ).replaceWith( this.childNodes );
+ } );
+ return this;
+ }
+} );
+
+
+jQuery.expr.pseudos.hidden = function( elem ) {
+ return !jQuery.expr.pseudos.visible( elem );
+};
+jQuery.expr.pseudos.visible = function( elem ) {
+ return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
+};
+
+
+
+
+// Support: Safari 8 only
+// In Safari 8 documents created via document.implementation.createHTMLDocument
+// collapse sibling forms: the second one becomes a child of the first one.
+// Because of that, this security measure has to be disabled in Safari 8.
+// https://bugs.webkit.org/show_bug.cgi?id=137337
+support.createHTMLDocument = ( function() {
+ var body = document.implementation.createHTMLDocument( "" ).body;
+ body.innerHTML = "<form></form><form></form>";
+ return body.childNodes.length === 2;
+} )();
+
+
+// Argument "data" should be string of html
+// context (optional): If specified, the fragment will be created in this context,
+// defaults to document
+// keepScripts (optional): If true, will include scripts passed in the html string
+jQuery.parseHTML = function( data, context, keepScripts ) {
+ if ( typeof data !== "string" ) {
+ return [];
+ }
+ if ( typeof context === "boolean" ) {
+ keepScripts = context;
+ context = false;
+ }
+
+ var base, parsed, scripts;
+
+ if ( !context ) {
+
+ // Stop scripts or inline event handlers from being executed immediately
+ // by using document.implementation
+ if ( support.createHTMLDocument ) {
+ context = document.implementation.createHTMLDocument( "" );
+
+ // Set the base href for the created document
+ // so any parsed elements with URLs
+ // are based on the document's URL (gh-2965)
+ base = context.createElement( "base" );
+ base.href = document.location.href;
+ context.head.appendChild( base );
+ } else {
+ context = document;
+ }
+ }
+
+ parsed = rsingleTag.exec( data );
+ scripts = !keepScripts && [];
+
+ // Single tag
+ if ( parsed ) {
+ return [ context.createElement( parsed[ 1 ] ) ];
+ }
+
+ parsed = buildFragment( [ data ], context, scripts );
+
+ if ( scripts && scripts.length ) {
+ jQuery( scripts ).remove();
+ }
+
+ return jQuery.merge( [], parsed.childNodes );
+};
+
+
+jQuery.offset = {
+ setOffset: function( elem, options, i ) {
+ var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
+ position = jQuery.css( elem, "position" ),
+ curElem = jQuery( elem ),
+ props = {};
+
+ // Set position first, in-case top/left are set even on static elem
+ if ( position === "static" ) {
+ elem.style.position = "relative";
+ }
+
+ curOffset = curElem.offset();
+ curCSSTop = jQuery.css( elem, "top" );
+ curCSSLeft = jQuery.css( elem, "left" );
+ calculatePosition = ( position === "absolute" || position === "fixed" ) &&
+ ( curCSSTop + curCSSLeft ).indexOf( "auto" ) > -1;
+
+ // Need to be able to calculate position if either
+ // top or left is auto and position is either absolute or fixed
+ if ( calculatePosition ) {
+ curPosition = curElem.position();
+ curTop = curPosition.top;
+ curLeft = curPosition.left;
+
+ } else {
+ curTop = parseFloat( curCSSTop ) || 0;
+ curLeft = parseFloat( curCSSLeft ) || 0;
+ }
+
+ if ( isFunction( options ) ) {
+
+ // Use jQuery.extend here to allow modification of coordinates argument (gh-1848)
+ options = options.call( elem, i, jQuery.extend( {}, curOffset ) );
+ }
+
+ if ( options.top != null ) {
+ props.top = ( options.top - curOffset.top ) + curTop;
+ }
+ if ( options.left != null ) {
+ props.left = ( options.left - curOffset.left ) + curLeft;
+ }
+
+ if ( "using" in options ) {
+ options.using.call( elem, props );
+
+ } else {
+ curElem.css( props );
+ }
+ }
+};
+
+jQuery.fn.extend( {
+
+ // offset() relates an element's border box to the document origin
+ offset: function( options ) {
+
+ // Preserve chaining for setter
+ if ( arguments.length ) {
+ return options === undefined ?
+ this :
+ this.each( function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ } );
+ }
+
+ var rect, win,
+ elem = this[ 0 ];
+
+ if ( !elem ) {
+ return;
+ }
+
+ // Return zeros for disconnected and hidden (display: none) elements (gh-2310)
+ // Support: IE <=11 only
+ // Running getBoundingClientRect on a
+ // disconnected node in IE throws an error
+ if ( !elem.getClientRects().length ) {
+ return { top: 0, left: 0 };
+ }
+
+ // Get document-relative position by adding viewport scroll to viewport-relative gBCR
+ rect = elem.getBoundingClientRect();
+ win = elem.ownerDocument.defaultView;
+ return {
+ top: rect.top + win.pageYOffset,
+ left: rect.left + win.pageXOffset
+ };
+ },
+
+ // position() relates an element's margin box to its offset parent's padding box
+ // This corresponds to the behavior of CSS absolute positioning
+ position: function() {
+ if ( !this[ 0 ] ) {
+ return;
+ }
+
+ var offsetParent, offset, doc,
+ elem = this[ 0 ],
+ parentOffset = { top: 0, left: 0 };
+
+ // position:fixed elements are offset from the viewport, which itself always has zero offset
+ if ( jQuery.css( elem, "position" ) === "fixed" ) {
+
+ // Assume position:fixed implies availability of getBoundingClientRect
+ offset = elem.getBoundingClientRect();
+
+ } else {
+ offset = this.offset();
+
+ // Account for the *real* offset parent, which can be the document or its root element
+ // when a statically positioned element is identified
+ doc = elem.ownerDocument;
+ offsetParent = elem.offsetParent || doc.documentElement;
+ while ( offsetParent &&
+ ( offsetParent === doc.body || offsetParent === doc.documentElement ) &&
+ jQuery.css( offsetParent, "position" ) === "static" ) {
+
+ offsetParent = offsetParent.parentNode;
+ }
+ if ( offsetParent && offsetParent !== elem && offsetParent.nodeType === 1 ) {
+
+ // Incorporate borders into its offset, since they are outside its content origin
+ parentOffset = jQuery( offsetParent ).offset();
+ parentOffset.top += jQuery.css( offsetParent, "borderTopWidth", true );
+ parentOffset.left += jQuery.css( offsetParent, "borderLeftWidth", true );
+ }
+ }
+
+ // Subtract parent offsets and element margins
+ return {
+ top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
+ left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
+ };
+ },
+
+ // This method will return documentElement in the following cases:
+ // 1) For the element inside the iframe without offsetParent, this method will return
+ // documentElement of the parent window
+ // 2) For the hidden or detached element
+ // 3) For body or html element, i.e. in case of the html node - it will return itself
+ //
+ // but those exceptions were never presented as a real life use-cases
+ // and might be considered as more preferable results.
+ //
+ // This logic, however, is not guaranteed and can change at any point in the future
+ offsetParent: function() {
+ return this.map( function() {
+ var offsetParent = this.offsetParent;
+
+ while ( offsetParent && jQuery.css( offsetParent, "position" ) === "static" ) {
+ offsetParent = offsetParent.offsetParent;
+ }
+
+ return offsetParent || documentElement;
+ } );
+ }
+} );
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
+ var top = "pageYOffset" === prop;
+
+ jQuery.fn[ method ] = function( val ) {
+ return access( this, function( elem, method, val ) {
+
+ // Coalesce documents and windows
+ var win;
+ if ( isWindow( elem ) ) {
+ win = elem;
+ } else if ( elem.nodeType === 9 ) {
+ win = elem.defaultView;
+ }
+
+ if ( val === undefined ) {
+ return win ? win[ prop ] : elem[ method ];
+ }
+
+ if ( win ) {
+ win.scrollTo(
+ !top ? val : win.pageXOffset,
+ top ? val : win.pageYOffset
+ );
+
+ } else {
+ elem[ method ] = val;
+ }
+ }, method, val, arguments.length );
+ };
+} );
+
+// Support: Safari <=7 - 9.1, Chrome <=37 - 49
+// Add the top/left cssHooks using jQuery.fn.position
+// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
+// Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347
+// getComputedStyle returns percent when specified for top/left/bottom/right;
+// rather than make the css module depend on the offset module, just check for it here
+jQuery.each( [ "top", "left" ], function( _i, prop ) {
+ jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
+ function( elem, computed ) {
+ if ( computed ) {
+ computed = curCSS( elem, prop );
+
+ // If curCSS returns percentage, fallback to offset
+ return rnumnonpx.test( computed ) ?
+ jQuery( elem ).position()[ prop ] + "px" :
+ computed;
+ }
+ }
+ );
+} );
+
+
+// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
+jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
+ jQuery.each( {
+ padding: "inner" + name,
+ content: type,
+ "": "outer" + name
+ }, function( defaultExtra, funcName ) {
+
+ // Margin is only for outerHeight, outerWidth
+ jQuery.fn[ funcName ] = function( margin, value ) {
+ var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
+ extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
+
+ return access( this, function( elem, type, value ) {
+ var doc;
+
+ if ( isWindow( elem ) ) {
+
+ // $( window ).outerWidth/Height return w/h including scrollbars (gh-1729)
+ return funcName.indexOf( "outer" ) === 0 ?
+ elem[ "inner" + name ] :
+ elem.document.documentElement[ "client" + name ];
+ }
+
+ // Get document width or height
+ if ( elem.nodeType === 9 ) {
+ doc = elem.documentElement;
+
+ // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
+ // whichever is greatest
+ return Math.max(
+ elem.body[ "scroll" + name ], doc[ "scroll" + name ],
+ elem.body[ "offset" + name ], doc[ "offset" + name ],
+ doc[ "client" + name ]
+ );
+ }
+
+ return value === undefined ?
+
+ // Get width or height on the element, requesting but not forcing parseFloat
+ jQuery.css( elem, type, extra ) :
+
+ // Set width or height on the element
+ jQuery.style( elem, type, value, extra );
+ }, type, chainable ? margin : undefined, chainable );
+ };
+ } );
+} );
+
+
+jQuery.fn.extend( {
+
+ bind: function( types, data, fn ) {
+ return this.on( types, null, data, fn );
+ },
+ unbind: function( types, fn ) {
+ return this.off( types, null, fn );
+ },
+
+ delegate: function( selector, types, data, fn ) {
+ return this.on( types, selector, data, fn );
+ },
+ undelegate: function( selector, types, fn ) {
+
+ // ( namespace ) or ( selector, types [, fn] )
+ return arguments.length === 1 ?
+ this.off( selector, "**" ) :
+ this.off( types, selector || "**", fn );
+ },
+
+ hover: function( fnOver, fnOut ) {
+ return this
+ .on( "mouseenter", fnOver )
+ .on( "mouseleave", fnOut || fnOver );
+ }
+} );
+
+jQuery.each(
+ ( "blur focus focusin focusout resize scroll click dblclick " +
+ "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+ "change select submit keydown keypress keyup contextmenu" ).split( " " ),
+ function( _i, name ) {
+
+ // Handle event binding
+ jQuery.fn[ name ] = function( data, fn ) {
+ return arguments.length > 0 ?
+ this.on( name, null, data, fn ) :
+ this.trigger( name );
+ };
+ }
+);
+
+
+
+
+// Support: Android <=4.0 only
+// Make sure we trim BOM and NBSP
+// Require that the "whitespace run" starts from a non-whitespace
+// to avoid O(N^2) behavior when the engine would try matching "\s+$" at each space position.
+var rtrim = /^[\s\uFEFF\xA0]+|([^\s\uFEFF\xA0])[\s\uFEFF\xA0]+$/g;
+
+// Bind a function to a context, optionally partially applying any
+// arguments.
+// jQuery.proxy is deprecated to promote standards (specifically Function#bind)
+// However, it is not slated for removal any time soon
+jQuery.proxy = function( fn, context ) {
+ var tmp, args, proxy;
+
+ if ( typeof context === "string" ) {
+ tmp = fn[ context ];
+ context = fn;
+ fn = tmp;
+ }
+
+ // Quick check to determine if target is callable, in the spec
+ // this throws a TypeError, but we will just return undefined.
+ if ( !isFunction( fn ) ) {
+ return undefined;
+ }
+
+ // Simulated bind
+ args = slice.call( arguments, 2 );
+ proxy = function() {
+ return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
+ };
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || jQuery.guid++;
+
+ return proxy;
+};
+
+jQuery.holdReady = function( hold ) {
+ if ( hold ) {
+ jQuery.readyWait++;
+ } else {
+ jQuery.ready( true );
+ }
+};
+jQuery.isArray = Array.isArray;
+jQuery.parseJSON = JSON.parse;
+jQuery.nodeName = nodeName;
+jQuery.isFunction = isFunction;
+jQuery.isWindow = isWindow;
+jQuery.camelCase = camelCase;
+jQuery.type = toType;
+
+jQuery.now = Date.now;
+
+jQuery.isNumeric = function( obj ) {
+
+ // As of jQuery 3.0, isNumeric is limited to
+ // strings and numbers (primitives or objects)
+ // that can be coerced to finite numbers (gh-2662)
+ var type = jQuery.type( obj );
+ return ( type === "number" || type === "string" ) &&
+
+ // parseFloat NaNs numeric-cast false positives ("")
+ // ...but misinterprets leading-number strings, particularly hex literals ("0x...")
+ // subtraction forces infinities to NaN
+ !isNaN( obj - parseFloat( obj ) );
+};
+
+jQuery.trim = function( text ) {
+ return text == null ?
+ "" :
+ ( text + "" ).replace( rtrim, "$1" );
+};
+
+
+
+// Register as a named AMD module, since jQuery can be concatenated with other
+// files that may use define, but not via a proper concatenation script that
+// understands anonymous AMD modules. A named AMD is safest and most robust
+// way to register. Lowercase jquery is used because AMD module names are
+// derived from file names, and jQuery is normally delivered in a lowercase
+// file name. Do this after creating the global so that if an AMD module wants
+// to call noConflict to hide this version of jQuery, it will work.
+
+// Note that for maximum portability, libraries that are not jQuery should
+// declare themselves as anonymous modules, and avoid setting a global if an
+// AMD loader is present. jQuery is a special case. For more information, see
+// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
+
+if ( typeof define === "function" && define.amd ) {
+ define( "jquery", [], function() {
+ return jQuery;
+ } );
+}
+
+
+
+
+var
+
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$;
+
+jQuery.noConflict = function( deep ) {
+ if ( window.$ === jQuery ) {
+ window.$ = _$;
+ }
+
+ if ( deep && window.jQuery === jQuery ) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+};
+
+// Expose jQuery and $ identifiers, even in AMD
+// (trac-7102#comment:10, https://github.com/jquery/jquery/pull/557)
+// and CommonJS for browser emulators (trac-13566)
+if ( typeof noGlobal === "undefined" ) {
+ window.jQuery = window.$ = jQuery;
+}
+
+
+
+
+return jQuery;
+} );
diff --git a/src/util.rs b/src/util.rs
@@ -7,6 +7,7 @@ use rocket::{
response::{self, Responder},
Request, Response,
};
+use serde::de::{self, DeserializeOwned, Deserializer, MapAccess, SeqAccess, Visitor};
use std::{error, io::Cursor, ops::Deref, string::ToString};
use tokio::{
runtime::Handle,
@@ -208,7 +209,9 @@ impl<'r, R: 'r + Responder<'r, 'static> + Send> Responder<'r, 'static> for Cache
res.set_raw_header("Cache-Control", cache_control_header);
let time_now = chrono::Local::now();
let expiry_time = time_now
- .checked_add_signed(chrono::Duration::seconds(self.ttl.try_into().unwrap()))
+ .checked_add_signed(
+ chrono::TimeDelta::try_seconds(self.ttl.try_into().unwrap()).unwrap(),
+ )
.expect("Duration add overflowed");
res.set_raw_header("Expires", format_datetime_http(&expiry_time));
Ok(res)
@@ -256,16 +259,6 @@ pub fn get_uuid() -> String {
uuid::Uuid::new_v4().to_string()
}
use std::str::FromStr;
-#[inline]
-fn upcase_first(s: &str) -> String {
- let mut c = s.chars();
- c.next().map_or_else(String::new, |f| {
- let mut val = f.to_uppercase().collect::<String>();
- val.push_str(c.as_str());
- val
- })
-}
-#[inline]
fn lcase_first(s: &str) -> String {
let mut c = s.chars();
c.next().map_or_else(String::new, |f| {
@@ -304,32 +297,37 @@ fn format_datetime_http(dt: &DateTime<Local>) -> String {
// offset (which would always be 0 in UTC anyway)
expiry_time.to_rfc2822().replace("+0000", "GMT")
}
-use serde::de::{self, DeserializeOwned, Deserializer, MapAccess, SeqAccess, Visitor};
use serde_json::{self, Value};
type JsonMap = serde_json::Map<String, Value>;
-#[derive(Deserialize)]
-pub struct UpCase<T: DeserializeOwned> {
- #[serde(deserialize_with = "upcase_deserialize")]
+#[derive(Serialize, Deserialize)]
+pub struct LowerCase<T: DeserializeOwned> {
+ #[serde(deserialize_with = "lowercase_deserialize")]
#[serde(flatten)]
pub data: T,
}
-// https://github.com/serde-rs/serde/issues/586
-fn upcase_deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
+impl Default for LowerCase<Value> {
+ fn default() -> Self {
+ Self { data: Value::Null }
+ }
+}
+
+pub fn lowercase_deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: DeserializeOwned,
D: Deserializer<'de>,
{
- let d = deserializer.deserialize_any(UpCaseVisitor)?;
+ let d = deserializer.deserialize_any(LowerCaseVisitor)?;
T::deserialize(d).map_err(de::Error::custom)
}
-struct UpCaseVisitor;
+struct LowerCaseVisitor;
-impl<'de> Visitor<'de> for UpCaseVisitor {
+impl<'de> Visitor<'de> for LowerCaseVisitor {
type Value = Value;
- fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("an object or an array")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
@@ -339,7 +337,7 @@ impl<'de> Visitor<'de> for UpCaseVisitor {
let mut result_map = JsonMap::new();
while let Some((key, value)) = map.next_entry()? {
- result_map.insert(upcase_first(key), upcase_value(value));
+ result_map.insert(_process_key(key), convert_json_key_lcase_first(value));
}
Ok(Value::Object(result_map))
@@ -351,39 +349,47 @@ impl<'de> Visitor<'de> for UpCaseVisitor {
let mut result_seq = Vec::<Value>::new();
while let Some(value) = seq.next_element()? {
- result_seq.push(upcase_value(value));
+ result_seq.push(convert_json_key_lcase_first(value));
}
Ok(Value::Array(result_seq))
}
}
-fn upcase_value(value: Value) -> Value {
- if let Value::Object(map) = value {
- let mut new_value = Value::Object(serde_json::Map::new());
- for (key, val) in map {
- let processed_key = _process_key(&key);
- new_value[processed_key] = upcase_value(val);
- }
- new_value
- } else if let Value::Array(array) = value {
- // Initialize array with null values
- let mut new_value = Value::Array(vec![Value::Null; array.len()]);
- for (index, val) in array.into_iter().enumerate() {
- new_value[index] = upcase_value(val);
- }
- new_value
- } else {
- value
- }
-}
-
// Inner function to handle a special case for the 'ssn' key.
// This key is part of the Identity Cipher (Social Security Number)
fn _process_key(key: &str) -> String {
match key.to_lowercase().as_ref() {
- "ssn" => "SSN".into(),
- _ => self::upcase_first(key),
+ "ssn" => "ssn".into(),
+ _ => self::lcase_first(key),
+ }
+}
+
+#[derive(Clone, Debug, Deserialize)]
+#[serde(untagged)]
+pub enum NumberOrString {
+ Number(i64),
+ String(String),
+}
+
+impl NumberOrString {
+ pub fn into_string(self) -> String {
+ match self {
+ Self::Number(n) => n.to_string(),
+ Self::String(s) => s,
+ }
+ }
+ pub fn into_i32(self) -> Result<i32, crate::Error> {
+ use std::num::ParseIntError as PIE;
+ match self {
+ Self::Number(n) => match i32::try_from(n) {
+ Ok(n) => Ok(n),
+ Err(_) => err!("Number does not fit in i32"),
+ },
+ Self::String(s) => s
+ .parse()
+ .map_err(|e: PIE| crate::Error::new("Can't convert to number", e.to_string())),
+ }
}
}
@@ -430,32 +436,38 @@ pub fn convert_json_key_lcase_first(src_json: Value) -> Value {
match src_json {
Value::Array(elm) => {
let mut new_array: Vec<Value> = Vec::with_capacity(elm.len());
+
for obj in elm {
new_array.push(convert_json_key_lcase_first(obj));
}
Value::Array(new_array)
}
+
Value::Object(obj) => {
let mut json_map = JsonMap::new();
- for tup in obj {
- match tup {
+ for (key, value) in obj {
+ match (key, value) {
(key, Value::Object(elm)) => {
- let inner_value = convert_json_key_lcase_first(Value::Object(elm.clone()));
- json_map.insert(lcase_first(key.as_str()), inner_value);
+ let inner_value = convert_json_key_lcase_first(Value::Object(elm));
+ json_map.insert(_process_key(&key), inner_value);
}
+
(key, Value::Array(elm)) => {
let mut inner_array: Vec<Value> = Vec::with_capacity(elm.len());
for inner_obj in elm {
- inner_array.push(convert_json_key_lcase_first(inner_obj.clone()));
+ inner_array.push(convert_json_key_lcase_first(inner_obj));
}
- json_map.insert(lcase_first(key.as_str()), Value::Array(inner_array));
+
+ json_map.insert(_process_key(&key), Value::Array(inner_array));
}
+
(key, value) => {
- json_map.insert(lcase_first(key.as_str()), value.clone());
+ json_map.insert(_process_key(&key), value);
}
}
}
+
Value::Object(json_map)
}
value @ (Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_)) => value,