https://www.pumaslove.com/seo/resource/id/c0da1be46ddba6eaaf24d701675f643d.js?ver=1640949085
Last Checked: Jun 16, 2023, 22:21 EDT
IP Address: | 69.192.139.233 |
ASN #: | AS20940 AKAMAI-ASN1, NL |
Location: | Unknown, Unknown, Unknown |
URL Reputation: |
|
Other submissions on 69.192.139.233:
-
https://www.flirtysparks.com/aff.php?dynamicpage=all_wlp_5st_vid_a_d_sound&utm_bo=1&a_bid=4fb1bd2a&utm_source=facebook&utm_campaign=crrsale&utm_content=21090&tds_reason=freeVipapprovednokey=2Fadv&tds_reason=direct&tds_campaign=b0450kly&tds_id=b0450kly_lp_a_1608557363838_nd&tds_oid=46121&dci=435c5ed38e8f1761ff90ec8af1fa8f77c754241b&data2=ah3VMF0i6h&utm_term=4st&s1=adv&utm_funnel=tds&tds_host=look4loves.com&utm_ex=a&utm_content=Desktop&dynamicpage=all_wlp_passion_versus_t&p_tds_cid=&utm_source=intc&utm_campaign=d3b0d71b
-
https://www.withu4ever.com/aff.php?%20dynamicpage=all_wlp_5st_mod_a_d&utm_bo=1&a_bid=4fb1bd2%C2%A0in_passion_c&utm_source=brand&utm_medium=web&utm_campaign=search&utm_term=us&text=brand&ppc_cp=1000
-
https://www.pumaslove.com/aff.php?dynamicpage=all_wlp_5st_vid_a_d_sound&utm_bo=1&a_bid=4fb1bd2a&utm_source=facebook&utm_campaign=crrsale&utm_content=21090&tds_reason=freeVipapprovednokey=2Fadv&tds_reason=direct&tds_campaign=b0450kly&tds_id=b0450kly_lp_a_1608557363838_nd&tds_oid=46121&dci=435c5ed38e8f1761ff90ec8af1fa8f77c754241b&data2=ah3VMF0i6h&utm_term=4st&s1=adv&utm_funnel=tds&tds_host=look4loves.com&utm_ex=a&utm_content=Desktop&dynamicpage=all_wlp_passion_versus_t&p_tds_cid=&utm_source=intc&utm_campaign=d3b0d71b/nov22
-
https://www.pumaslove.com/user/view/id/fdd17b1729b06c1b980dde92a689e6bc
-
https://www.pumaslove.com/search/country:AUS&tab:new_members&sortType:lastvisit&gender:1&ageFrom:18&ageTo:35&country:AUS&location:MacKay%2C%204740&distance:50&searchbar_wphoto:wphoto&wphoto%5B%5D:0&photoLevel%5B%5D:0&photoLevel%5B%5D:1&photoLevel%5B%5D:2&photoLevel%5B%5D:3&photoLevel%5B%5D:4&sexual_orientation%5B%5D:hetero
-
https://www.pumaslove.com/aff.php?dynamicpage=nav_wlp_5st_vid_a&utm_bo=1&a_bid=4fb1bd2a&utm_source=facebook&utm_campaign=crrsale&utm_content=91090&tds_reason=direct&utm_term=%7Butm_term%7D&_disAL=true&tds_bo_origin=lpaHR0cHM6Ly9iYW5nLXNleHkuY29tL3Rkcy9jcGEvcy80ODI3NzY4MGUzYTQ2M2QwYTU3ZDhlMjEwNmE2ZDhjMz9fX3Q9MTU2MDQzMjQyMzgyNSZfX2w9MzYwMA%3D%3D&b=1&tds_bo=1&utm_bo=1
-
https://www.flirtysparks.com/ppc.php?dynamicpage=all_wlp_5st_mod_a_d&utm_bo=1&a_bid=4fb1bd2a%C2%A0in_passion_c&utm_source=brand&utm_medium=web&utm_campaign=search&utm_term=us&text=brand&utm_sub=opnfnlconf&ppc_cp=1000000001&clkid=f133a822c23a56657c1f4a5612cc7842
-
https://m.flirtysparks.com/ppc.php?dynamicpage=all_wlp_5st_snapyellow_a&utm_bo=1&a_bid=4ct3ce8r&utm_mediums=web&utm_campaign=53F23927482
-
https://www.flirtysparks.com/aff.php?s1=int&dynamicpage=all_wlp_4st_product4_a&s3=1320827&utm_ex=b&tdsId=b2375koz_lp_b_1637072464101_mw&tds%20_campaign=b2375koz&dci=5805a2e8e31c0b9fdae3b516e32c102017e9f2bb&tds_host=privatesinglesmeet.com&utm_term=10&data3=%7Bdata3%7D&tds_path=%2Ftds%2Fint&tds_%20oid=46782&tds_ac_id=s6782yal&tds_cid=1aa641f9327e01eb1023b5a063209af9d5f10d2f&tds_id=b2375koz_lp_b_1637072464101_mw&h=1&_cbUrl=aHR0cHM6Ly9wcml2YXRlc2luZ2%20xlc21lZXQuY29tL3Rkcy9pbnQ%2FdXRtX2NvbnRlbnQ9MTAyNjEyJnRkc19ob3N0PXByaXZhdGVzaW5nbGVzbWVldC5jb20mcF90ZHNfY2lkPTQ4Y2I4YmVmMDY3OWYwOTI5YzdmOWRlMjRhZTZlZDI5ZDc1ZG%20QzNjkmdXRtX2NhbXBhaWduPWNmMjkzMjJiJnRkc19wX2NhbXBhaWduPWIxODE1eWFsJnV0bV90ZXJtPTEwJnRkc19yZWFzb249cmVzZXJ2ZWQmczM9MTMyMDgyNyZ0ZHNfY2FtcGFpZ249YjIzNzVrb3omZGNpPTU4MDVhMmU4ZTMxYzBiOWZkYW%20UzYjUxNmUzMmMxMDIwMTdlOWYyYmImdGRzX29pZD1tdyZ0ZHNJZD1iMjM3NWtvel90ZHNfc2l0ZV9ncm91cF9iXzE2MzcwNzI0NjQxMDEmdGRzX2FjX2lkPXM2NzgyeWFsJnRkc19wYXRoPSUyRnRkcyUyRmludCZ1dG1fc3ViPW9wbmZ%20ubGNvbmYmdGRzX2NpZD0xYWE2NDFmOTMyN2UwMWViMTAyM2I1YTA2MzIwOWFmOWQ1ZjEwZDJmJnRkc19pZD1iMjM3NWtvel90ZHNfc2l0ZV9ncm91cF9iXzE2MzcwNzI0NjQxMDEmdXRtX3NvdXJjZT1pbnQmczE9aW50JmRhdGEyPWVy%20cXRhNjFmM2JjYTQwMDA3ZTEzNSZkYXRhMz0lN0JkYXRhMyU3RCZ0ZHNUcmFmZmljPWJhY2tUcmFmZmljJnRkc1NvbHV0aW9uPW13&utm_content=102612&data2=erqta61f3bca40007e135&_disAL=true&utm_sub=opnfnlco%20nf&p_tds_cid=48cb8bef0679f0929c7f9de24ae6ed29d75dd369&tds_ao=1&tds_p_campaign=b1815yal&tds_reason=reserved&utm_campaign=cf29322b&utm_funnel=tds&utm_source=facebook
-
https://www.pumaslove.com/aff.php?dynamicpage=all_wlp_5st_tiktok_vid_a&utm_bo=1&a_bid=4fb1bd2a%C2%A0in_passion_c&utm_source=brand&utm_medium=web&utm_campaign=b7979yal&utm_term=GB&text=chat&ppc_cp=5859665984&clkid=f133a822c23a56657c1f4a5612cc78421
Other submissions on pumaslove.com:
-
https://www.pumaslove.com/aff.php?dynamicpage=all_wlp_5st_purple_a&utm_bo=1&a_bid=4ctce8r%20in_reason=VIPupgraded_c&utm_source=facebook&utm_medium=web&utm_disAL_campaign=search&utm_term=us&text=facebook&ppc_cp=1000000001&clkid=f133a822c23a5
-
http://pumaslove.com/
-
https://www.pumaslove.com/aff.php?tds_ao=1&dynamicpage=all_wlp_5st_halfphoto_v2_a&tds_cid=f96c2b612cbd12d86226faf679da2f8b57735360&btUrl=aHR0cHM6Ly9jbG9zZW1lZXR1cHMuY29tL3Rkcy9hZS9jYi9zLzdlMzZmNTgwZjA4OTJhNjIyZjkxYjUzZDkwZWFjZWMyP19fdD0xNjc3MjYzNDMwMTMzJl9fbD0zNjAw&tds_ac_id=s0792tok&p_tds_cid=&utm_content=MkRufiq&utm_ex=a&dci=e8b8298fd6cac1e6a21b8bfe57f99794d72dd085&tdsId=b5382yas_lp_a_1567437013893_bn&s3=%7Bsubid2%7D&tds_path=%2Ftds%2Fae&s1=ps&tds_oid=4375327&utm_source=intcc&utm_sub=opnfnl&tds_host=closemeetups.com&tds_reason=direct&tds_ps=a&_disAL=true&utm_funnel=tds&tds_id=b5382yas_lp_a_1567437013893_bn&utm_campaign=17f36e01&tds_campaign=b5382yas&data2=%7Bclickid%7D
-
http://pumaslove.com/
-
http://pumaslove.com/
-
https://pumaslove.com/MERON-NA-TAYO-MGA-TROPA-UNLIMITED-MESSAGE/CREATE/SETUP-ACCOUNT-FOR-FLIRTYSPARKS-PUMASLOVE-FOR-USA-CANADA-AUSTRALIA-UNITEDKINGDOM-FOR-SALE-MESSAGE-ME-SAGIMANA99@GMAIL.COM
-
http://pumaslove.com/buying-TEXTNOW-1000PESOS-PER-ACCOUNT-I-NEED-3ACCOUNT-.com/qqq
-
http://pumaslove.com/buying-TEXTNOW-1000PESOS-PER-ACCOUNT-I-NEED-3ACCOUNT///PMMEONHANGOUTSIHAVETEXTNOW
-
http://pumaslove.com/buying-TEXTNOW-1000PESOS-PER-ACCOUNT-I-NEED-3ACCOUNT--OKAYNA-NAKABILI---NA--SALMAT--SHAVEEEE
-
http://pumaslove.com/user/loginFromCompletePage/withoutConfirm/verified/2?mobToWeb
Previous checks:
Domain Name: pumaslove.com Registry Domain ID: 2146616059_DOMAIN_COM-VRSN Registrar WHOIS Server: whois.rrpproxy.net Registrar URL: Updated Date: 2021-10-25T16:40:54Z Creation Date: 2017-07-25T15:18:10Z Registrar Registration Expiration Date: 2030-07-25T15:18:10Z Registrar: Key-Systems GmbH Registrar IANA ID: 269 Registrar Abuse Contact Email: abusereport@key-systems.net Registrar Abuse Contact Phone: +49.68949396850 Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited Registry Registrant ID: Not Available From Registry Registrant Name: On behalf of pumaslove.com OWNER Registrant Organization: c/o whoisproxy.com Registrant Street: 604 Cameron Street Registrant City: Alexandria Registrant State/Province: VA Registrant Postal Code: 22314 Registrant Country: US Registrant Phone: +64.48319528 Registrant Phone Ext: Registrant Fax: Registrant Fax Ext: Registrant Email: 12960c50c1c5afdd3b112aa92644b176d74baa293f76af5888b93b1333af0ce9@pumaslove.com.whoisproxy.org Registry Admin ID: Not Available From Registry Admin Name: On behalf of pumaslove.com ADMIN Admin Organization: c/o whoisproxy.com Admin Street: 604 Cameron Street Admin City: Alexandria Admin State/Province: VA Admin Postal Code: 22314 Admin Country: US Admin Phone: +64.48319528 Admin Phone Ext: Admin Fax: Admin Fax Ext: Admin Email: 12960c50c1c5afdd3b112aa92644b176d74baa293f76af5888b93b1333af0ce9@pumaslove.com.whoisproxy.org Registry Tech ID: Not Available From Registry Tech Name: On behalf of pumaslove.com TECH Tech Organization: c/o whoisproxy.com Tech Street: 604 Cameron Street Tech City: Alexandria Tech State/Province: VA Tech Postal Code: 22314 Tech Country: US Tech Phone: +64.48319528 Tech Phone Ext: Tech Fax: Tech Fax Ext: Tech Email: 12960c50c1c5afdd3b112aa92644b176d74baa293f76af5888b93b1333af0ce9@pumaslove.com.whoisproxy.org Registry Billing ID: Not Available From Registry Billing Name: On behalf of pumaslove.com BILLING Billing Organization: c/o whoisproxy.com Billing Street: 604 Cameron Street Billing City: Alexandria Billing State/Province: VA Billing Postal Code: 22314 Billing Country: US Billing Phone: +64.48319528 Billing Phone Ext: Billing Fax: Billing Fax Ext: Billing Email: 12960c50c1c5afdd3b112aa92644b176d74baa293f76af5888b93b1333af0ce9@pumaslove.com.whoisproxy.org Name Server: ns5.dnsmadeeasy.com Name Server: ns6.dnsmadeeasy.com Name Server: ns7.dnsmadeeasy.com DNSSEC: unsigned Whoisprivacy: 1 URL of the ICANN WHOIS Data Problem Reporting System: https://wdprs.internic.net/ >>> Last update of WHOIS database: 2023-06-17T02:21:05Z <<< For more information on Whois status codes, please visit https://www.icann.org/epp To contact the registered registrant please proceed to: https://www.domain-contact.org This data is provided by for information purposes, and to assist persons obtaining information about or related to domain name registration records. does not guarantee its accuracy. By submitting a WHOIS query, you agree that you will use this data only for lawful purposes and that, under no circumstances, you will use this data to 1) allow, enable, or otherwise support the transmission of mass unsolicited, commercial advertising or solicitations via E-mail (spam) or 2) enable high volume, automated, electronic processes that apply to this WHOIS server. These terms may be changed without prior notice. By submitting this query, you agree to abide by this policy.
-
GET0 Timed out waiting for a response.
https://www.pumaslove.com/assets/aa6ebe3d/pumaslove_favicon.ico
- https://www.pumaslove.com/favicon.ico https://www.pumaslove.com/assets/aa6ebe3d/pumaslove_favicon.ico
<html><head><link rel="stylesheet" href="resource://content-accessible/plaintext.css"></head><body><pre>"use strict"; // Utils class Utils { isTouchDevice() { return "ontouchstart" in window || navigator.MaxTouchPoints > 0 || navigator.msMaxTouchPoints > 0 ? true : false; } isObject(value) { return value && typeof value === "object" && value.constructor === Object; } mergeDeep(target, ...sources) { if (!sources.length) return target; const source = sources.shift(); if (this.isObject(target) && this.isObject(source)) { for (const key in source) { if (this.isObject(source[key])) { if (!target[key]) Object.assign(target, { [key]: {} }); this.mergeDeep(target[key], source[key]); } else { Object.assign(target, { [key]: source[key] }); } } } return this.mergeDeep(target, ...sources); } debounce(f, ms) { let isReady = false; let wrapper = function () { if (isReady) return; f.apply(this, arguments); isReady = true; setTimeout(function () { isReady = false; }, ms); }; return wrapper; } throttle(func, ms) { let isThrottled = false, savedArgs, savedThis; function wrapper() { if (isThrottled) { savedArgs = arguments; savedThis = this; return; } func.apply(this, arguments); isThrottled = true; setTimeout(function () { isThrottled = false; if (savedArgs) { wrapper.apply(savedThis, savedArgs); savedArgs = savedThis = null; } }, ms); } return wrapper; } } // Validator class Validator { constructor({ props, formContainer, fields }) { this.formContainer = formContainer; this.props = props; this.fields = fields; this.storage = {}; this.errorList = new Set(); this.lastErrorName = ""; this.isSubmitted = false; this.setEvents(); } setEvents() { this.formContainer.addEventListener("field-validate", (e) => { this.highlight(e.detail.response); }); } prepareFormData(fields) { const formDataList = fields.reduce((acc, item) => { // cancel a repeated request with the same value if (this.storage[item.name] === item.element.value) { return acc; } // new formData for new request const formData = new FormData(); formData.append("ajax", "register-form"); formData.append("scenario", item.validateRule.scenario); formData.append(item.fieldName, item.element.value); acc.push(formData); this.storage[item.name] = item.element.value; this.lastErrorName = item.errorName; return acc; }, []); return formDataList; } prepareRequests(dataList) { return dataList.map((request) => { const data = { method: "POST", body: request, }; return fetch(this.props.actionURL, data); }); } validate(fields) { // Avoid requests if empty field const notEmptyFields = this.props.hasValidationEmptyFields ? this.checkEmptyFields(fields) : fields; // Send requests const fieldsData = this.prepareFormData(notEmptyFields); const promises = this.prepareRequests(fieldsData); return Promise.all(promises) .then((responses) => { return Promise.all(responses.map((res) => res.json())); }) .then((responses) => { responses.forEach((response) => { if (Array.isArray(response) && !response.length) { this.unhighlight(this.lastErrorName); } else { const eventFieldValidate = new CustomEvent("field-validate", { detail: { response }, }); this.formContainer.dispatchEvent(eventFieldValidate); } }); return this.errorList; }) .catch((err) => { console.error(err); }); } checkEmptyFields(fields) { const emptyFields = fields.filter((field) => field.element.value.length === 0).map((field) => field.errorName); emptyFields.forEach((name) => this.renderEmptyFieldError(name)); return fields.filter((field) => field.element.value.length !== 0); } renderEmptyFieldError(name) { if (!window.jqueryValidationMessages) return; // Errors object from backend const fieldName = this.fields.filter((field) => field.errorName === name).map((field) => field.fieldName); const errorName = window.jqueryValidationMessages[fieldName]; const errorMessage = errorName && errorName.required; this.renderErrorMessage(name, errorMessage); } getErrorElements(name) { const { fieldContainer, errorContainer } = this.props; const errorField = this.fields.find((field) => field.errorName === name); if (!errorField) return; const parent = errorField.element.closest(fieldContainer); const container = parent.querySelector(errorContainer); return { errorField, parent, container, }; } resetErrors(name) { const { errorClass } = this.props; const { parent, container } = this.getErrorElements(name); container.innerHTML = ""; parent.classList.remove(errorClass); this.errorList.delete(name); } renderErrorMessage(name, message) { const { errorClass } = this.props; const { parent, container } = this.getErrorElements(name); container.dataset.errorName = name; container.innerHTML = `<p>${message}</p>`; parent.classList.add(errorClass); this.errorList.add(name); } unhighlight(name) { this.resetErrors(name); } highlight(data) { // data example = { UserForm_age: ["Enter your age"] } const [errorName] = Object.keys(data); const [errorMessage] = Object.values(data); this.renderErrorMessage(errorName, errorMessage[0]); } } // User Recovery class UserRecovery { constructor({ formContainer, props: { userRecoveryProps } }) { this.formContainer = formContainer; this.userRecoveryProps = userRecoveryProps; this.init(); } init() { this.emailElement = this.formContainer.querySelector(this.userRecoveryProps.emailElement); const tokenElement = this.formContainer.querySelector(this.userRecoveryProps.tokenElement); this.token = tokenElement ? tokenElement.value : ""; this.setEvents(); } setEvents() { this.formContainer.querySelector(this.userRecoveryProps.errorContainer).addEventListener("click", (e) => { e.preventDefault(); if (e.target.matches(this.userRecoveryProps.passwordElement)) { this.recoveryPassword(e.target.href); } if (e.target.matches(this.userRecoveryProps.confirmElement)) { this.recoveryConfirm(e.target.href); } if (e.target.matches(this.userRecoveryProps.cancelElement)) { this.recoveryCancel(e.target.href); } }); } recoveryRequest(url, formData) { const headers = new Headers({ "x-requested-with": "XMLHttpRequest", }); const options = { method: "POST", headers: headers, body: formData, }; fetch(url, options) .then((res) => res.json()) .then(({ status, data, meta }) => { // redirect to captcha if (status === "error" && meta.code === 302 && meta.redirect) { this.redirectToCaptcha(meta.redirect); return; } const message = status === "success" ? data.message : meta.description.description; this.showErrorMessage(message); }) .catch((err) => { console.error(err); }); } recoveryPassword(url) { const formData = new FormData(); formData.append("RecoveryForm[email]", this.emailElement.value); this.recoveryRequest(url, formData); } recoveryConfirm(url) { const recoveryId = url.substr(-32); url = url.replace("/id/" + recoveryId, ""); const formData = new FormData(); formData.append("id", recoveryId); formData.append("YII_CSRF_TOKEN", this.token); this.recoveryRequest(url, formData); } recoveryCancel(url) { this.recoveryRequest(url); } showErrorMessage(msg) { this.errorElement = this.formContainer.querySelector(this.userRecoveryProps.errorElement); this.errorElement && (this.errorElement.innerHTML = `<p>${msg}</p>`); } redirectToCaptcha(url) { const exp = new RegExp(/\/\/(www|m)/); if (!exp.test(url)) { url = location.protocol + "//" + location.host + (url[0] === "/" ? url : "/" + url); } window.location.href = url; } } //Login class LoginForm extends Utils { constructor(formContainer, options = {}) { super(); this.formContainer = formContainer; this.props = this.initProps(options); this.state = this.initState(this.formContainer); this.errorList = []; this.lastErrorContainer = null; this.isSubmitted = false; this.setEvents(); } initProps(options) { const defaults = { login: { formNamespace: "LoginForm", actionURL: "/site/checkLogin", formElement: ".login-form", emailElement: ".login-email-field", emailError: '[data-error-name="email"]', msisdnError: '[data-error-name="msisdn"]', passwordElement: ".login-password-field", passwordError: '[data-error-name="password"]', submitElement: ".login-form-submit", }, recovery: { formNamespace: "RecoveryForm", actionURL: "/account/remindPassword", formElement: ".recovery-form", emailElement: ".recovery-email-field", emailError: '[data-error-name="email"]', emailSuccess: '[data-success-name="email"]', submitElement: ".recovery-form-submit", }, fieldContainer: ".form-item", fieldElement: ".form-input input", errorClass: "error-field", validClass: "valid-field", }; return this.mergeDeep(defaults, options); } initState(formContainer) { const loginForm = formContainer.querySelector(this.props.login.formElement); const recoveryForm = formContainer.querySelector(this.props.recovery.formElement); const fields = this.initFields(loginForm, recoveryForm); return { loginForm, recoveryForm, fields, }; } initFields(loginForm, recoveryForm) { const { login, recovery } = this.props; const loginEmailField = loginForm.querySelector(login.emailElement); const loginEmailError = loginForm.querySelector(login.emailError); const loginMsisdnError = loginForm.querySelector(login.msisdnError); const loginPasswordField = loginForm.querySelector(login.passwordElement); const loginPasswordError = loginForm.querySelector(login.passwordError); const recoveryEmailField = recoveryForm.querySelector(recovery.emailElement); const recoveryEmailError = recoveryForm.querySelector(recovery.emailError); const recoveryEmailSuccess = recoveryForm.querySelector(recovery.emailSuccess); return { [loginEmailField.name]: { element: loginEmailField, value: null, error: loginEmailError, }, [`${login.formNamespace}[msisdn]`]: { element: loginEmailField, value: null, error: loginMsisdnError, }, [loginPasswordField.name]: { element: loginPasswordField, value: null, error: loginPasswordError, }, [recoveryEmailField.name]: { element: recoveryEmailField, value: null, error: recoveryEmailError, success: recoveryEmailSuccess, }, }; } setEvents() { const { login, recovery } = this.props; const { loginForm, recoveryForm } = this.state; const loginSubmitBtn = loginForm.querySelector(login.submitElement); loginSubmitBtn.addEventListener("click", (e) => { e.preventDefault(); if (this.hasSameValues(loginForm)) return; this.submit(login.actionURL, loginForm); }); const recoverySubmitBtn = recoveryForm.querySelector(recovery.submitElement); recoverySubmitBtn.addEventListener("click", (e) => { e.preventDefault(); this.validate(recovery.actionURL, recoveryForm); }); const forgotPassword = this.formContainer.querySelector(".recovery-password-btn"); forgotPassword.addEventListener("click", () => { this.switchToForm("login", "recovery"); }); const backToLogin = this.formContainer.querySelector(".login-switch-btn"); backToLogin.addEventListener("click", () => { this.switchToForm("recovery", "login"); }); } hasSameValues(form) { const sameValues = []; form.querySelectorAll(this.props.fieldElement).forEach(({ name, value }) => { sameValues.push(this.state.fields[name].value === value); }); return sameValues.every(Boolean); } switchToForm(from, to) { const fromForm = this.formContainer.querySelector(this.props[from].formElement); const toForm = this.formContainer.querySelector(this.props[to].formElement); fromForm.classList.remove("visible"); fromForm.classList.add("hidden"); toForm.classList.remove("hidden"); toForm.classList.add("visible"); fromForm.querySelector(this.props.fieldContainer).classList.remove(this.props.errorClass); const { fields } = this.state; if (fields[`${this.props[from].formNamespace}[email]`].element.value !== "") { fields[`${this.props[to].formNamespace}[email]`].element.closest(".form-item").classList.add("is-focused"); fields[`${this.props[from].formNamespace}[email]`].element.closest(".form-item").classList.remove("is-focused"); } fields[`${this.props[to].formNamespace}[email]`].element.value = fields[`${this.props[from].formNamespace}[email]`].element.value; toForm.querySelector(this.props[to].submitElement).click(); } redirect(url) { const exp = new RegExp(/\/\/(www|m)/); if (!exp.test(url)) { url = location.protocol + "//" + location.host + (url[0] === "/" ? url : "/" + url); } window.location.href = url; } prepareFetchOptions(form) { const body = new FormData(form); const headers = new Headers({ "x-requested-with": "XMLHttpRequest", }); return { method: "POST", headers, body, }; } validate(url, form) { // this.updateState(form); const options = this.prepareFetchOptions(form); return fetch(url, options) .then((res) => res.json()) .then(({ data, status, meta }) => { if (status === "error") { if (meta.code === 302 && meta.redirect) { this.redirect(meta.redirect); return; } this.renderErrors(meta.description); } if (status === "success") { if (data.valid) { this.renderErrors(data); return; } this.errorList = []; } return this.errorList; }) .catch((err) => { throw err; }); } renderErrors(data) { const { login, recovery, errorClass, validClass } = this.props; const { fields } = this.state; if (data.type) { const errorName = `${login.formNamespace}[${data.type}]`; this.renderMessage(fields[errorName].error, data.message, errorClass); this.errorList.push(errorName); } if (data.type === "email") { fields["LoginForm[password]"].error.closest(this.props.fieldContainer).classList.add(errorClass); } if (data.type === "password") { fields["LoginForm[email]"].error.closest(this.props.fieldContainer).classList.add(errorClass); } const recoveryName = `${recovery.formNamespace}[email]`; if (data.email) { this.renderMessage(fields[recoveryName].error, data.email[0], errorClass, validClass); } if (data.valid) { this.renderMessage(fields[recoveryName].success, data.message, validClass, errorClass); } } renderMessage(container, message, errorClass, validClass) { this.lastErrorContainer && (this.lastErrorContainer.innerHTML = ""); this.lastErrorContainer = container; container.innerHTML = `<p>${message}</p>`; container.closest(this.props.fieldContainer).classList.add(errorClass); container.closest(this.props.fieldContainer).classList.remove(validClass); } async submit(url, form) { if (!this.isSubmitted) { const errors = await this.validate(url, form); if (!errors.length && !this.isSubmitted) { this.isSubmitted = true; form.submit(); } } } } /* Fields Blur & focus events */ class FieldEvents { constructor(root, options = {}) { this.props = this.initProps(options); this.fields = this.setFields(root); this.setEvents(); } initProps(options) { const defaults = { fieldContainer: ".form-item", activeClass: "is-visible", focusClass: "is-focused", select: { selectContainer: ".form-select", selectedValue: "select-value", selectDropdown: "select-dropdown", selectDropdownItem: "select-item", }, }; return { ...defaults, ...options }; } setFields(root) { const fieldsArray = Array.from(root.querySelectorAll(this.props.fieldContainer)); const fields = fieldsArray.reduce((acc, formItem) => { formItem.querySelectorAll("input, select").forEach((el) => acc.push(el)); return acc; }, []); return fields; } setEvents() { const { activeClass, select: { selectContainer, selectedValue, selectDropdownItem }, } = this.props; const inputs = this.fields.filter((input) => input.tagName === "INPUT"); const selects = this.fields.filter((select) => select.tagName === "SELECT"); // Input change inputs.forEach((input) => { input.value ? this.addFocus(input) : this.removeFocus(input); input.addEventListener("focus", (e) => this.addFocus(e.currentTarget)); input.addEventListener("blur", ({ currentTarget }) => { currentTarget.value ? this.addFocus(currentTarget) : this.removeFocus(currentTarget); }); }); selects && this.renderCustomSelects(selects); // Selects change let openedSelectedValue; selects && selects.forEach((select) => { const parent = select.closest(selectContainer); const selected = parent.querySelector(`.${selectedValue}`); select.addEventListener("change", ({ currentTarget }) => { selected.textContent = currentTarget[currentTarget.selectedIndex].innerHTML; }); // show/hide dropdown selected.addEventListener("click", (e) => { const openedSelect = document.querySelector(`${selectContainer}.${this.props.activeClass}`); openedSelectedValue = openedSelect && openedSelect.querySelector(`.${selectedValue}`); if (openedSelect && openedSelectedValue !== e.currentTarget) { openedSelect.classList.remove(this.props.activeClass); } parent.classList.toggle(activeClass); e.stopPropagation(); }); // select-item click parent.querySelectorAll(`.${selectDropdownItem}`).forEach((item) => { item.addEventListener("click", ({ currentTarget }) => { const gender = currentTarget.dataset.genderValue; const value = currentTarget.getAttribute("value"); selected.textContent = currentTarget.textContent; select.value = value; if (gender) { select.dataset.genderSelected = gender; select.querySelector(`option[value="${value}"][data-gender-value="${gender}"]`).selected = true; } parent.classList.remove(activeClass); this.addFocus(currentTarget); select.dispatchEvent(new Event("change")); }); }); }); // Close opened selects document.addEventListener("click", (e) => { if (e.target !== openedSelectedValue) { const openedSelect = document.querySelector(`${selectContainer}.${this.props.activeClass}`); if (openedSelect) { openedSelect.classList.remove(this.props.activeClass); } } }); } addFocus(el) { const { fieldContainer, focusClass } = this.props; el.closest(fieldContainer).classList.add(focusClass); } removeFocus(el) { const { fieldContainer, focusClass } = this.props; el.closest(fieldContainer).classList.remove(focusClass); } renderCustomSelects(selects) { const { selectContainer, selectedValue, selectDropdown, selectDropdownItem } = this.props.select; selects.forEach((select) => { const dropdown = document.createElement("div"); dropdown.className = selectDropdown; const selected = document.createElement("div"); selected.className = selectedValue; select.querySelectorAll("option").forEach((option) => { if (option.selected) { selected.textContent = option.textContent; this.addFocus(select); } let clone = `<div class="${selectDropdownItem}" value="${option.value}">${option.textContent}</div>`; if (option.dataset.genderValue) { clone = `<div class="${selectDropdownItem}" value="${option.value}" data-gender-value="${option.dataset.genderValue}">${option.textContent}</div>`; } dropdown.insertAdjacentHTML("beforeend", clone); }); select.closest(selectContainer).appendChild(selected); select.closest(selectContainer).appendChild(dropdown); }); } } // Multi Step class MultiStep { constructor(validator, { formContainer, fields, props }) { this.validator = validator; this.formContainer = formContainer; this.fields = fields; this.props = props; this.init(); } init() { const { stepContainer, formControls, pagination, activeClass } = this.props; this.stepsList = this.formContainer.querySelectorAll(stepContainer); this.maxStep = this.stepsList.length; this.currentStep = Array.from(this.stepsList).find((step) => step.classList.contains(activeClass)); this.currentStepIndex = !this.currentStep ? 0 : Array.from(this.stepsList).findIndex((step) => step.classList.contains(activeClass)); this.setCurrentStepData(this.currentStepIndex); this.nextBtn = this.formContainer.querySelector(formControls.nextElement); this.prevBtn = this.formContainer.querySelector(formControls.prevElement); if (pagination.exist) { this.initPagination(); } this.setEvents(); } setEvents() { const onNextClick = (e) => { e.preventDefault(); e.target.focus(); if (!event.detail || event.detail == 1) { this.next(); } }; this.nextBtn && this.nextBtn.addEventListener("click", onNextClick); const onPrevClick = (e) => { e.preventDefault(); this.prev(); }; this.prevBtn && this.prevBtn.addEventListener("click", onPrevClick); } setCurrentStepData(index) { this.stepsList[index].classList.add(this.props.activeClass); this.currentStep = this.stepsList[index]; this.formContainer.dataset.currentStepIndex = index + 1; document.body.dataset.currentStepName = this.currentStep.dataset.stepName; } getStepNodes(index) { const step = this.stepsList[index]; if (!step) { return; } return Array.from(step.querySelectorAll(`[name^="${this.props.formNamespace}"]`)).map((node) => node.name); } getCurrentStepFields(index) { const currentStepNodes = this.getStepNodes(index); return this.fields.filter((field) => currentStepNodes.includes(field.fieldName)); } getCurrentStepRequiredFields(index) { const currentStepRequiredNodes = this.getStepNodes(index); return this.fields.filter((field) => { return field.validateRule && field.validateRule.required && currentStepRequiredNodes.includes(field.fieldName); }); } getPrevStepFields(index) { const prevStepNodes = this.getStepNodes(index - 1); if (!prevStepNodes) return null; return this.fields.filter((field) => prevStepNodes.includes(field.fieldName)); } getNextStepFields(index) { const nextStepNodes = this.getStepNodes(index + 1); if (!nextStepNodes) return null; return this.fields.filter((field) => nextStepNodes.includes(field.fieldName)); } showNextStep() { if (this.currentStepIndex < this.stepsList.length - 1) { this.currentStepIndex++; this.stepBy(this.currentStepIndex, "step-next"); this.dispatchMultistepEvent("step-next"); this.dispatchMultistepEvent("step-change"); } } next() { const currentStepFields = this.getCurrentStepRequiredFields(this.currentStepIndex); if (currentStepFields.length) { this.validator.isNext = true; this.validator .validate(currentStepFields) .then((errorList) => { const currentStepErrorNames = currentStepFields.map((field) => field.errorName).filter((field) => errorList.has(field)); if (!errorList.size || !currentStepErrorNames.length) { // Show next step this.showNextStep(); } }) .catch((err) => { console.error(err); return err; }); } else { // Show next step if missing required fields this.showNextStep(); } } prev() { if (this.currentStepIndex > 0) { this.currentStepIndex--; this.stepBy(this.currentStepIndex); this.dispatchMultistepEvent("step-prev"); this.dispatchMultistepEvent("step-change"); } } stepBy(index) { this.currentStep.classList.remove(this.props.activeClass); this.setCurrentStepData(index); } dispatchMultistepEvent(name = "step-change") { const event = new CustomEvent(name, { detail: { maxStep: this.maxStep, currentStep: this.currentStep, currentStepIndex: this.currentStepIndex, currentStepFields: this.getCurrentStepFields(this.currentStepIndex), prevStepFields: this.getPrevStepFields(this.currentStepIndex), nextStepFields: this.getNextStepFields(this.currentStepIndex), }, }); this.formContainer.dispatchEvent(event); } initPagination() { const { root, container, item } = this.props.pagination; let items = `<div class="${container}">`; for (let i = 1; i <= this.maxStep; i++) { items += `<div class="${item}" data-pagination-index="${i}"><span>${i}</span></div>`; } items += "</div>"; root.insertAdjacentHTML("beforeend", items); this.paginationContainer = this.formContainer.querySelector(`.${container}`); this.paginations = [...this.paginationContainer.children]; this.updatePagination(0); this.formContainer.addEventListener("step-next", (e) => this.updatePagination(e.detail.currentStepIndex)); this.formContainer.addEventListener("step-prev", (e) => this.updatePagination(e.detail.currentStepIndex)); } updatePagination(i) { const { root, currentClass, visitedClass } = this.props.pagination; this.paginations.forEach((el) => { el.classList.remove(currentClass); el.classList.remove(visitedClass); }); this.paginations .filter((el, index) => index <= i) .forEach((el) => { el.classList.add(visitedClass); }); this.paginationContainer.children[i].classList.remove(visitedClass); this.paginationContainer.children[i].classList.add(currentClass); } } // scenario: loginOnly // scenario: ageOnly // scenario: email // scenario: passwordOnly class Regform extends Utils { constructor(formContainer, options = {}) { super(); this.formContainer = formContainer; this.props = this.initProps(options); this.state = this.initState(this.formContainer, this.props); this.init(); } init() { const { formElement, gender, orientation } = this.props; this.form = this.formContainer.querySelector(formElement); this.validator = new Validator(this.state); this.userRecovery = new UserRecovery(this.state); if (this.props.hasMultiSteps) { this.multiStep = new MultiStep(this.validator, this.state); } this.setValidatorEvents(); // Set default genderSelect or genderButtons this.props.genderBtns.exist ? this.initGenderBtns() : this.initGenderSelect(); // Set default gender & orientation this.setOrientation(gender, orientation); // Set FieldEvents new FieldEvents(this.formContainer); if (this.getFieldsElement("password")) { // Visible/hide password this.initPasswordIcon(this.getFieldsElement("password")); } } initProps(options) { const defaults = { formNamespace: "UserForm", formElement: ".register-hidden-form", actionURL: "/user/register", stepContainer: ".form-step-item", fieldContainer: ".form-item", errorContainer: ".form-error-block", errorClass: "error-field", validClass: "valid-field", activeClass: "is-active", passwordIcon: ".password-icon", gender: "male", orientation: "hetero", hasMultiSteps: false, formControls: { submitElement: ".submit-btn", nextElement: ".next-btn", prevElement: ".prev-btn", }, genderBtns: { exist: false, activeClass: "is-active", genderAttr: "[data-gender]", partnerGenderAttr: "[data-partner-gender]", }, pagination: { exist: false, root: this.formContainer, container: "pagination-block", item: "pagination-item", currentClass: "is-current", visitedClass: "is-visited", }, hasValidationEmptyFields: false, fields: { gender: { name: "gender", validateRule: { required: false, }, }, partnerGender: { name: "partnerGender", validateRule: { required: false, }, }, sexual_orientation: { name: "sexual_orientation", validateRule: { required: false, }, }, age: { name: "age", validateRule: { required: true, scenario: "ageOnly", }, }, screenname: { name: "login", validateRule: { required: true, scenario: "loginOnly", }, }, location: { name: "location", validateRule: { required: true, scenario: "location", }, }, email: { name: "email", validateRule: { required: true, scenario: "email", }, }, password: { name: "password", validateRule: { required: true, scenario: "passwordOnly", }, }, }, userRecoveryProps: { formNamespace: "UserForm", errorContainer: `[data-step-name="email"] .form-error-block`, errorElement: `div[data-error-name="UserForm_email"]`, cancelElement: "#recoveryCancelUser", confirmElement: "#recoveryConfirm", passwordElement: "#recoveryPassword", tokenElement: 'input[name="YII_CSRF_TOKEN"]', emailElement: `input[name="UserForm[email]"]`, }, }; return this.mergeDeep(defaults, options); } initState(formContainer, props) { const fields = this.setFields(); return { props, formContainer, fields, }; } setFields() { return Object.values(this.props.fields).reduce((acc, field) => { const element = this.formContainer.querySelector(`[name='${this.props.formNamespace}[${field.name}]']:not([type='hidden'])`); if (element) { const fieldName = element.name; const extendedField = { element, fieldName, errorName: `${this.props.formNamespace}_${field.name}`, }; acc.push(Object.assign(field, extendedField)); } return acc; }, []); } getFieldsElement(name) { const elem = this.state.fields.find((field) => field.name === name); return elem && elem.element; } setFieldValue(name, value) { const element = this.state.fields.find((field) => field.name === name).element; element.value = value; element.dispatchEvent(new Event("blur")); element.dispatchEvent(new Event("change")); } updateFieldValue(fields) { const nodes = fields.map((field) => field.element); nodes.forEach(({ name, value }) => { const input = this.form.querySelector(`[name='${name}']`); input && (input.value = value); }); } setValidatorEvents() { const validateRequiredFields = this.state.fields.filter(({ validateRule }) => validateRule && validateRule.required); const inputs = validateRequiredFields.filter((input) => input.element.tagName === "INPUT"); const selects = validateRequiredFields.filter((select) => select.element.tagName === "SELECT"); // Event listener for Submit FORM this.submitBtn = this.formContainer.querySelector(this.props.formControls.submitElement); this.submitBtn.addEventListener("click", (e) => { e.preventDefault(); if (e.detail > 1) return; // prevent multiple clicks on submit button e.target.focus(); // safari fix relatedTarget this.submit(validateRequiredFields); }); // Event listeners for BLUR INPUTS const eventInputBlur = new Event("field-blur"); const handleInputBlur = (e) => { // do not trigger BLUR event if the button is clicked if ( (this.submitBtn && this.submitBtn === e.relatedTarget) || (this.multiStep && this.multiStep.nextBtn && this.multiStep.nextBtn === e.relatedTarget) ) { return; } e.target.dispatchEvent(eventInputBlur); }; inputs.forEach((input) => { input.element.addEventListener("field-blur", () => { // Updating the value of hidden inputs this.updateFieldValue([input]); this.validator.validate([input]); }); input.element.addEventListener("blur", handleInputBlur); }); // Event listeners for CHANGE SELECTS const eventSelectChange = new Event("field-change"); selects.forEach((select) => { select.element.addEventListener("field-change", () => { // Updating the value of hidden inputs this.updateFieldValue([select]); this.validator.validate([select]); }); select.element.addEventListener("change", (e) => { e.target.dispatchEvent(eventSelectChange); }); }); // update to actual state values on validate this.formContainer.addEventListener("field-validate", () => { this.updateFieldValue(validateRequiredFields); }); } initPasswordIcon(passwordElement) { this.formContainer.querySelector(this.props.passwordIcon).addEventListener("click", (e) => { e.target.classList.toggle("active"); passwordElement.type == "password" ? passwordElement.setAttribute("type", "text") : passwordElement.setAttribute("type", "password"); }); } initGenderBtns() { const { activeClass, genderAttr, partnerGenderAttr } = this.props.genderBtns; const { gender, formNamespace, orientation } = this.props; const genderBtns = this.formContainer.querySelectorAll(genderAttr); const partnerGenderBtns = this.formContainer.querySelectorAll(partnerGenderAttr); const triggerActiveClass = (currentTarget, btns) => { this.formContainer.querySelectorAll(btns).forEach((btn) => btn.classList.remove(activeClass)); currentTarget.classList.add(activeClass); }; const triggerPartnerGenderBtn = (gender) => { partnerGenderBtns.forEach((btn) => { if (orientation === "hetero") { btn.dataset.partnerGender === gender ? btn.classList.remove(activeClass) : btn.classList.add(activeClass); } else { btn.dataset.partnerGender === gender ? btn.classList.add(activeClass) : btn.classList.remove(activeClass); } }); }; // Set default active Gender btn const defaultGender = Array.from(genderBtns).find((btn) => btn.dataset.gender === gender); defaultGender.classList.add(activeClass); // Set default active PartnerGender btn triggerPartnerGenderBtn(gender); genderBtns.forEach((btn) => { btn.addEventListener("click", ({ currentTarget }) => { triggerActiveClass(currentTarget, genderAttr); this.setOrientation(currentTarget.dataset.gender, orientation); triggerPartnerGenderBtn(currentTarget.dataset.gender); }); }); partnerGenderBtns.forEach((btn) => { btn.addEventListener("click", ({ currentTarget }) => { triggerActiveClass(currentTarget, partnerGenderAttr); const currentGender = this.form.querySelector(`[name='${formNamespace}[gender]']`).value; this.changeOrientation(currentGender, currentTarget.dataset.partnerGender); }); }); } initGenderSelect() { const { gender, orientation } = this.props; const notRequiredFields = this.state.fields.filter(({ validateRule }) => validateRule && !validateRule.required); // change orientation const genderField = this.getFieldsElement("gender"); const partnerGenderField = this.getFieldsElement("partnerGender"); notRequiredFields.forEach((field) => { field.element.addEventListener("change", () => { this.updateFieldValue(notRequiredFields); }); const eventOrientationChange = new Event("orientation-change"); switch (field.name) { case "gender": // Set default value genderField.querySelector(`option[value="${gender}"]`).selected = true; // Event listener for Gender genderField.addEventListener("change", ({ target }) => { this.changeOrientation(target.value, partnerGenderField.value); }); break; case "partnerGender": // Set default value if (orientation === "homo") { partnerGenderField.querySelector(`option[value="${gender}"]`).selected = true; } else { const partnerValue = Array.from(partnerGenderField.querySelectorAll("option")).find((el) => el.value !== gender); partnerGenderField.querySelector(`option[value="${partnerValue.value}"]`).selected = true; } // Event listener for Gender partnerGenderField.addEventListener("change", ({ target }) => { this.changeOrientation(genderField.value, target.value); }); break; case "sexual_orientation": // Set default value const orientationField = this.getFieldsElement("sexual_orientation"); orientationField.querySelector(`option[value="${orientation}"][data-gender-value="${gender}"]`).selected = true; // Event listener for Orientation orientationField.addEventListener("orientation-change", ({ target }) => { target.dataset.genderSelected = target.selectedOptions[0].dataset.genderValue; this.setOrientation(target.dataset.genderSelected, target.value); }); orientationField.addEventListener("change", ({ target }) => { target.dispatchEvent(eventOrientationChange); }); } }); } changeOrientation(gender, partnerGender) { if (`${gender}-${partnerGender}` === "male-female") this.setOrientation("male", "hetero"); if (`${gender}-${partnerGender}` === "male-male") this.setOrientation("male", "homo"); if (`${gender}-${partnerGender}` === "female-male") this.setOrientation("female", "hetero"); if (`${gender}-${partnerGender}` === "female-female") this.setOrientation("female", "homo"); } setOrientation(gender, orientation) { this.form.querySelector(`[name='${this.state.props.formNamespace}[gender]']`).value = gender; this.form.querySelector(`[name='${this.state.props.formNamespace}[sexual_orientation]']`).value = orientation; document.body.dataset.orientation = `${orientation}-${gender}`; } async submit(fields) { // Prevent double submit form if (!this.isSubmitted) { // Updating the value of hidden inputs this.updateFieldValue(fields); const errors = await this.validator.validate(fields); if (!errors.size && !this.isSubmitted) { const eventFormSubmit = new Event("form-submit"); this.formContainer.dispatchEvent(eventFormSubmit); this.isSubmitted = true; this.form.submit(); } } } } </pre></body></html>