/* A plugin-template for AggressiveLayout to auto-login. It requires the following items to be set: api - the endpoint to use when logging in (either set it in options or it will use the layout's url). app_slug - to prefix anything app-specific like cookies and localStorage items It will set layout.login_token after successful login, and call its observers. Optional settings: can_create_account (bool) showLogin (bool) if you want to start with login instead of create. showLoginAtCreate (bool) to show the login button when creating an account TODO: describe your options! */ function APILogin(options) { if (!options) options = {} this.options = options if (options.api) this.api = options.api; this.showLogin = options.showLogin this.strings = { signupTitle : options.signupTitle || "#Get started by signing up", reason : options.reason || "", has_email_title : options.has_email_title || "#Thank you!", has_email_reason : options.has_email_reason || "Just set a password and name (for nicer emails) and we are done!", has_email_check_title : options.has_email_check_title || "#Thank you!", has_email_check_reason : options.has_email_check_reason || "Validating...", do_login_title : options.do_login_title || "##Log in", do_login_reason : options.do_login_reason || "Enter password to login.", forgotPassword: options.forgotPassword || "**Forgot Password?** Click to receive a password reset link by e-mail.", passwordResetButtonTitle: options.passwordResetButtonTitle || "Send password reset link", passwordResetButtonTitleAgain: options.passwordResetButtonTitleAgain || "Send new reset link" } this.minPasswordLength = options.minPasswordLength || 8 if (options.createFooter) this.createFooter = options.createFooter if (options.signupCheckboxes) this.signupCheckboxes = options.signupCheckboxes if (options.loginFooter) this.loginFooter = options.loginFooter this.can_create_account = options.can_create_account this.showLoginAtCreate = options.showLoginAtCreate this.fromColor = options.fromColor || "rgb(0, 255, 0)" this.toColor = options.toColor || "rgb(0, 170, 0)" this.fromColorHover = options.fromColorHover || "rgb(255, 0, 0)" this.toColorHover = options.toColorHover || "rgb(170, 0, 0)" this.buttonTextColor = options.buttonTextColor || "black" this.buttonTextColorHover = options.buttonTextColorHover || "white" if (!window.localStorage) { window.localStorage = { getItem : function(){ return null }, setItem : function(variable){}, removeItem : function(variable){}} } this.app_slug = options.app_slug || ""; this.login_token_key = this.app_slug + "tok" this.login_token = localStorage.getItem(this.login_token_key) || "" //remove states this.elementEnum = Object.freeze({ email : 1, password : 2, fullname : 3, loginPassword: 4 }) this.deleteEnum = { start: 0, warningRevealed: 1, deleted: 2 } this.deleteState = this.deleteEnum.start //this gives us observe and callObservers - is there a better way of doing this? AutoObserve.inherit(this) //to get the JSON to layout, just use the layoutJSONData property Object.defineProperty(this, "layoutJSONData", { get() { if (!this.JSONData) this.initialLayout() return this.JSONData }, set(value) { var doUpdate = this.JSONData //update only if we had one before. this.JSONData = value if (doUpdate) this.layout.updateJSONData() } }) if (options.layout) { options.layout.addPlugin(this); //auto-called when adding plugins (if done at documentLoaded) this.setupFunction(); } } APILogin.prototype = { setLayout: function(layout) { this.app_slug = layout.app_slug || this.app_slug //we add ourself to the plugin dict layout.plugins.apiLogin = this this.layout = layout }, setupFunction: function() { if (!this.api) this.api = this.layout.API_URL; if (location.host != "localhost" && location.protocol !== "https:") { //console.log("redirect"); location.protocol = "https:"; } this.email_key = this.app_slug + "email" //We save the session in our cookies, so this is not needed! - why are you using this when you are sending cookies? //allowing JS to see cookies is a safety risk so we need to let people restrict that - it also means we can't send tokens like this, it should just say "is_logged_in"... if (this.login_token == "null") this.login_token = null if (this.login_token) this.layout.login_token = this.login_token //buttons inside text must have some margins. this.layout.applyCSS( { ".selector": { first_key: "first_value"}, ".adLoginButton > p": { margin: "0.2em"}, ".adLoginButton": { backgroundImage: "linear-gradient(" + this.fromColor + ", " + this.toColor + ")", color: this.buttonTextColor, margin: "10px", fontWeight: "bolder", padding: "10px 15px", textDecoration: "none", borderRadius: 30 }, ".input_field": { width: "49%", marginBottom: "5px", marginRight: "unset", padding: "0.2em 0.2em", fontSize: "1.1em" }, ".signupContainer": { display: "flex", flexWrap: "wrap", "justify-content": "space-between" }, ".passwordField": { flexGrow: 2 }, }); if (!this.layout.smallerSheet) this.layout.smallerSheet = this.layout.createStyleSheet( { media: "all and (max-width: " + AUTO_DEVICE_WIDTH.smallerPhone + "px)"}) this.layout.applyCSS( { ".input_field": { width: "100%", }, }, null, this.layout.smallerSheet); if (this.layout.hasHover()) { //add css with hover this.layout.applyCSS ({ ".adLoginButton:hover": { backgroundImage: "linear-gradient(" + this.fromColorHover + ", " + this.toColorHover + ")", color: this.buttonTextColorHover }, }) } if (!this.layout.cssStyleSelectorExists(".separator")) this.layout.setCSSFromSelector(".separator", { position: "relative", width: "70%", margin: "20px 0px 20px 15%", borderBottomWidth: "1px", borderBottomStyle: "solid", borderBottomColor: "rgb(0, 0, 0), left: 15%" }) if (!this.layout.cssStyleSelectorExists(".errorBox")) this.layout.setCSSFromSelector(".errorBox", { backgroundColor: "rgba(255, 184, 5, 0.85098)", border: "3px solid black", fontSize: "1.5em", fontWeight: "bold", margin: "1em", padding: "1em" }) if (!this.layout.cssStyleSelectorExists(".input_field")) this.layout.setCSSFromSelector(".input_field", { width: "250px", marginRight: "0.5em", padding: "0.2em 0.2em", fontSize: "1.1em" }) var email = localStorage.getItem(this.email_key) || "" this.layoutOptions = { errorDiv: { var: "autoLoginErrorDiv", className : "errorBox", style : { display: "none"} }, emailTextField: { var: "autoLoginEmail", html: email, textFieldType: "email", className: "input_field", autoType: AUTO_TYPE.textField, autocomplete: "username", placeholder: "Email", target: this, didReturn: "performLogin", didChange: "emailDidChange", changeOnInput: 1 }, passwordTextField: { var: "autoLoginPassword", className: "input_field", autoType: AUTO_TYPE.textField, autocomplete: "current-password", placeholder: "Password", textFieldType: "password", target: this, didReturn: "performLogin" }, createButton: { var: "autoLoginCreateButton", html: "Create account", autoType: AUTO_TYPE.button, className: "adLoginButton", plain: 1, target: this, listener: "performCreateAccount" }, loginButton: { var: "autoLoginButton", html: "Log in", autoType: AUTO_TYPE.button, className: "adLoginButton", plain: 1, target: this, listener: "performLogin"}, //signupButton: { var: "autoSignupButton", html: "Signup", autoType: AUTO_TYPE.button, className: "adLoginButton", plain: 1, target: this, listener: "performSignup"}, signupButton: { autoType: AUTO_TYPE.anchorLink, key: "page", value: "signup", html: "Signup", className: "plainButton adLoginButton", autoListeners: { click: { function: "performSignup", target: this, }}, style: { fontWeight: "bolder" } }, fullNameTextField: { var: "autoLoginFullname", className: "input_field passwordField", autoType: AUTO_TYPE.textField, allowAutocorrect: 1, allowAutocomplete: 1, allowAutocapitalize: 1, placeholder: "Name", target: this, didReturn: "performCreateAccount" }, passwordResetButton: { listener: "passwordResetButtonDidClick", autoType: AUTO_TYPE.button, html: this.strings.passwordResetButtonTitle, target: this, className: "adLoginButton", plain: 1 }, forgotPasswordResult: { var: "forgotPasswordResult" } } //not sure if this is needed. this.initialLayout() if (this.login_token) this.callObservers() }, anchorChangeCallback: function(event, actions) { if (actions && (actions.verifyToken || actions.accTok)) { this.initialLayout(actions) } }, //when starting (or starting over), we set the json. initialLayout: function(tokenDict) { if (!this.layout) return; //not setup var actions = this.layout.parseQueryString(this.layout.anchor); var signup = (actions.page && actions.page == "signup") if (!tokenDict) tokenDict = this.layout.parseQueryString(window.location.hash, "#"); this.verifyToken = tokenDict && tokenDict.verifyToken; this.accTok = tokenDict && tokenDict.accTok; this.hideErrorDiv() if (this.accTok && !this.verifyToken) { this.JSONData = [{ text: "Verifying account..." }] this.layout.postAPI({"verifyAccount": this.accTok }, { target : this }, this.api) } else if (this.verifyToken) { this.JSONData = [ { text : "#Reset password" }, { var : "autoLoginErrorDiv", className : "errorBox", style : { display : "none"} }, { var : "ad_verify_token_password_div", className : "input_field", autoType : AUTO_TYPE.textField, autocomplete : "new-password", placeholder : "New Password", textFieldType : "password", target: this, didReturn : "confirmNewPasswordReset" }, { autoType: AUTO_TYPE.button, html : "Reset Password", target: this, listener : "confirmNewPasswordReset", className: "adLoginButton", plain: 1 }, ] } else if (this.login_token) { this.JSONData = [{ text: "You are logged in!" }] } else { if (!signup && (this.showLogin || !this.can_create_account)) { this.showLoginDiv() } else //signup first { this.showCreateAccount() } } //update layout this.layout.updateJSONData() }, hideErrorDiv: function() { var errorDiv = this.layout.elements.autoLoginErrorDiv if (errorDiv) errorDiv.style.display = "none" }, showLoginDiv: function(update) { var json = [ this.layoutOptions.errorDiv, { text: this.strings.do_login_title }, { text: this.strings.do_login_reason }, { className: "signupContainer", children: [ this.layoutOptions.emailTextField, this.layoutOptions.passwordTextField, ]}, { type : "br" }, this.layoutOptions.loginButton, this.layoutOptions.signupButton, { className: "separator"}, { var: "forgotPasswordLabel", text: this.strings.forgotPassword}, this.layoutOptions.passwordResetButton, ] //footers needs to be dynamic per app if (this.loginFooter) { json.push({ className: "separator"}) json.appendArray(this.loginFooter) } this.JSONData = json if (update) this.layout.updateJSONData() }, showCreateAccount: function() { //make shallow copies so the original isn't modified var emailTextField = Object.assign({}, this.layoutOptions.emailTextField) emailTextField.didReturn = "performCreateAccount" var json = [ this.layoutOptions.errorDiv, { text: this.strings.signupTitle }, { text: this.strings.reason }, emailTextField, this.showLoginAtCreate ? this.layoutOptions.loginButton : null, this.layoutOptions.createButton, ] if (this.createFooter) { json.push({ className: "separator"}) json.appendArray(this.createFooter) } this.JSONData = json }, showCreateAccountFull: function() { var emailTextField = Object.assign({}, this.layoutOptions.emailTextField) var passwordTextField = Object.assign({}, this.layoutOptions.passwordTextField) var fullNameTextField = Object.assign({}, this.layoutOptions.fullNameTextField) emailTextField.didReturn = "performCreateAccount" passwordTextField.didReturn = "performCreateAccount" fullNameTextField.didReturn = "performCreateAccount" var container = { className: "signupContainer", children: [ emailTextField, passwordTextField, fullNameTextField, ]} var json = [ this.layoutOptions.errorDiv, { text: this.strings.has_email_title }, { text: this.strings.has_email_reason }, container, { type : "br" }, this.layoutOptions.createButton ] //auto-create signup boxes so they can be dynamic per app but also be applied when creating accounts. if (this.signupCheckboxes) { //standard keys are: newsletter json.push({ className: "separator"}) for (var index = 0; index < this.signupCheckboxes.length; index++) { var item = this.signupCheckboxes[index]; json.push({ type: "label", className: "autoCheckBoxLabel", style : { display : "inlineBlock" }, children: [ { autoType : AUTO_TYPE.checkBox, checked: item.checked, name: item.name, var: "loginCheckBox_" + item.name }, { type : "span", html: item.html } ] }) json.push({ type: "br" }) } } if (this.createFooter) { json.push({ className: "separator"}) json.appendArray(this.createFooter) } this.layoutJSONData = json if (this.layout.elements.autoLoginPassword) this.layout.elements.autoLoginPassword.focus(); }, nope_layoutFunction: function() { //handle delete state var deleteDiv = this.layout.elements.autoDeleteAccountDiv; if (deleteDiv && deleteDiv.parentNode) { this.updateDeleteDiv() } }, confirmNewPasswordReset: function() { var password = this.layout.elements.ad_verify_token_password_div.value if (!password || password.length <= 5) { this.showError("Passwords must have 8 characters with both lowercase and uppercase characters") return; } this.layoutJSONData = [ { text : "#Reset password" }, this.layoutOptions.errorDiv, { text : "Verifying reset token..." }, ] var postDictionary = { resetPasswordConfirm : 1, verifyToken : this.verifyToken, password : password } this.layout.postAPI(postDictionary, { target : this }, this.api); }, passwordResetButtonDidClick: function() { var elements = this.layout.elements; var email = elements.autoLoginEmail.value if (!email || email.length < 4) { this.showError("Enter email in the field above") return; } this.layout.modifyAnchor("verifyToken", null, "#") this.layoutJSONData = [ this.layoutOptions.errorDiv, { text : "##Password link is sent!\n\nDon't forget to check your spam folder, if it got caught there by misstake." }, ] var postDictionary = { resetPassword: 1, email : email } this.layout.postAPI(postDictionary, { target : this }, this.api); }, performSignup: function(event, element) { //console.log("hello!"); var actions = this.layout.parseQueryString(this.layout.anchor); if (actions.page && actions.page == "signup") { //console.log("it was here!"); if (this.emailExists) { this.showError("This account already exists") this.layout.elements.autoLoginPassword.focus() } else this.performCreateAccount(event, element) } //signupButton: { autoType: AUTO_TYPE.anchorLink, key: "page", value: "signup", html: "Signup", className: "plainButton adLoginButton", style: { fontWeight: "bolder" } }, }, emailDidChange: function() { console.log("change!"); this.emailExists = false }, //Callback for everything that wants to login the user performLogin: function(event, element) { if (!this.validation(this.elementEnum.email)) return false //use loginPassword here so we don't have restrictions when logging in - only when creating if (!this.validation(this.elementEnum.loginPassword)) { if (this.showLoginAtCreate) { //if we allow to login when creating we must also run the same algorithm this.performCreateAccount(event, element) } return false; } if (!this.validation(this.elementEnum.email) || !this.validation(this.elementEnum.loginPassword)) return false; if (this.layout.elements.autoLoginErrorDiv) this.layout.elements.autoLoginErrorDiv.style.display = "none" var postDictionary = { doLogin: 1, password: this.layout.elements.autoLoginPassword.value, email: this.layout.elements.autoLoginEmail.value, } this.startSpinner(element || this.layout.elements.autoLoginButton); this.layout.postAPI(postDictionary, { target : this }, this.api); }, //the same for creating account performCreateAccount: function(event, element) { if (!this.validation(this.elementEnum.email)) return false if (!this.validation(this.elementEnum.password)) { //we don't have password, check or just layout! var email = this.layout.elements.autoLoginEmail.value if (!this.checkedEmails || !this.checkedEmails[email]) { this.layoutJSONData = [ this.layoutOptions.errorDiv, { text: this.strings.has_email_check_title }, { type: "span", html: this.strings.has_email_check_reason }, { autoType: AUTO_TYPE.spinner, style: { display: "inline-block", verticalAlign: "middle", marginRight : "10px", marginLeft : "10px"}} ] //checkAccountEmail responds with if exists (then we should login), or not (then we sign up). this.layout.postAPI({ checkAccountEmail: email }, { target : this, successCallback: "validateEmailSuccess", errorCallback: "errorCallback" }, this.layout.plugins.apiLogin.api); return false; } else { this.validateEmailSuccess(null, null, email) } } if (!this.validation(this.elementEnum.fullname)) { return } //all checks has passed! var elements = this.layout.elements elements.autoLoginErrorDiv.style.display = "none" var postDictionary = { password: elements.autoLoginPassword.value, email: elements.autoLoginEmail.value, doCreateAccount: 1, fullname: elements.autoLoginFullname.value, } if (this.signupCheckboxes) { for (var index = 0; index < this.signupCheckboxes.length; index++) { var item = this.signupCheckboxes[index]; var box = elements["loginCheckBox_" + item.name] if (box && box.checked) postDictionary[item.name] = 1 } } this.startSpinner(elements.autoLoginCreateButton); this.layout.postAPI(postDictionary, { target : this }, this.api); }, validateEmailSuccess: function(payload, request, email) { var emailExists; if (payload) { email = request.postDictionary.checkAccountEmail if (!this.checkedEmails) this.checkedEmails = {} this.checkedEmails[email] = payload emailExists = payload.exists } else emailExists = this.checkedEmails[email].exists this.emailExists = emailExists //remember states! if (emailExists) { this.showLoginDiv(true) } else { this.showCreateAccountFull() } }, //Callback for successful requests successCallback: function(payload, request) { this.layout.hideAutoError(); if (this.layout.elements.autoLoginErrorDiv && this.layout.elements.autoLoginErrorDiv.style) this.layout.elements.autoLoginErrorDiv.style.display = "none" if (!request) return; this.stopSpinner(); if (request.postDictionary.verifyAccount) { if (!payload.verifyAccount) { this.layoutJSONData = [ { className : "errorBox", html: "Could not verify account:\n
Token is outdated or there is a more recent token." } ] this.layout.modifyAnchor("accTok", null, "#") } else { this.layoutJSONData = [ { text : "#Verifying account" }, { var : "autoLoginErrorDiv", className : "errorBox", style : { display : "none"} }, { text: "Account is verified!" } ] this.layout.modifyAnchor("accTok", null, "#") if (this.layout.didVerify) this.layout.didVerify(); } if (payload.login) { //also login! this.setLoginToken(payload.login) if (this.layout.didLogin) this.layout.didLogin(); this.callObservers() this.layout.updateLayout(); } } else if (request.postDictionary.doCreateAccount || request.postDictionary.doLogin || request.postDictionary.resetPasswordConfirm) { this.setLoginToken(payload.login) //handle new verifyTokens and redirect if (request.postDictionary.resetPasswordConfirm) { this.layout.modifyAnchor("verifyToken", null, "#") } var redirect = null; if (location.search) { var query = this.layout.parseQueryString(location.search, "?") if (query && query.redirect) { redirect = query.redirect; //we have a redirect so we handle it ourselves location.href = redirect; return; } } if (payload.redirect) { location.href = redirect; return; } //login was successful - always call layout, since we always need to. if (this.layout.didLogin) this.layout.didLogin(); this.callObservers() this.layout.updateLayout(); } else if (request.postDictionary.doLogout) { this.setLoginToken(null) this.initialLayout() //always do this, since we always need to. if (this.layout.didLogout) this.layout.didLogout() this.callObservers() this.layout.updateLayout(); } }, ///Callback for unsuccessful requests errorCallback: function(payload, request) { this.stopSpinner(); if (payload && payload.error) { if (this.errorIsWithinLoginRange(payload.error) && this.layout.elements) { if (payload.error == 3001 || payload.error == 3014) { //no auth - need login this.setLoginToken(null) //and call observers to present loginDiv } else if (payload.error == 3012) { //token too old, request new if you want to reset password //location.hash = this.layout.modifyValueInQueryString(location.hash, "verifyToken", null, "#") var emailTextField = Object.assign({}, this.layoutOptions.emailTextField) emailTextField.didReturn = "passwordResetButtonDidClick" var passwordResetButton = Object.assign({}, this.layoutOptions.passwordResetButton) passwordResetButton.html = this.strings.passwordResetButtonTitleAgain this.layoutJSONData = [ this.getErrorJSON(payload.message), { type: "p" }, emailTextField, passwordResetButton, ] } else this.showError(payload.message); } else if (payload.message) this.layout.showAutoError(payload.message) } }, errorIsWithinLoginRange: function(errorCode) { return (errorCode >= 3000 && errorCode < 4000) || errorCode == 1002; }, //-------------functions ----------------- setLoginToken: function(login_token) { if (login_token) login_token = "isLoggedIn" this.login_token = login_token this.layout.login_token = this.login_token if (login_token) localStorage.setItem(this.login_token_key, this.login_token) else localStorage.removeItem(this.login_token_key) //call if we have any, we might or not update layout afterwards. this.callObservers() }, validation: function(elementEnum) { switch (elementEnum) { case this.elementEnum.email: if (!document.getElementById("autoLoginEmail")) return false; var email = this.layout.elements.autoLoginEmail.value; if (email.length < 6) { this.layout.elements.autoLoginEmail.focus(); this.showError("Enter an email"); return false; } localStorage.setItem(this.email_key, email); this.layoutOptions.emailTextField.html = email break; case this.elementEnum.password: //exist on visible page? var autoLoginPassword = document.getElementById("autoLoginPassword") if (!autoLoginPassword) return false; var password = this.layout.elements.autoLoginPassword.value; if (password.length <= this.minPasswordLength) { this.layout.elements.autoLoginPassword.focus(); this.showError("Enter a longer password, at least " + this.minPasswordLength + " characters") return false; } break; case this.elementEnum.loginPassword: //exist on visible page? var autoLoginPassword = document.getElementById("autoLoginPassword") if (!autoLoginPassword) return false; var password = this.layout.elements.autoLoginPassword.value; if (!password.length) { this.layout.elements.autoLoginPassword.focus(); this.showError("Enter a password") return false; } break; case this.elementEnum.fullname: if (!document.getElementById("autoLoginFullname")) return false; if (this.layout.elements.autoLoginFullname.value.length < 2) { this.layout.elements.autoLoginFullname.focus() this.showError("Fill in your name. It makes emails much more friendly.") return false; } break; default: } return true; }, getErrorJSON: function(error) { return { var : "autoLoginErrorDiv", className : "errorBox", html: error, style: { display: "block"} } }, showError: function(error) { if (this.layout.elements.autoLoginErrorDiv) { this.layout.elements.autoLoginErrorDiv.innerHTML = error; this.layout.elements.autoLoginErrorDiv.style.display = "" } else this.layout.showAutoError(error) }, startSpinner: function(button) { if (!this.loginSpinner) this.loginSpinner = this.layout.newSpinner({ style: { display: "inline-block", verticalAlign: "middle", marginRight : "10px", marginLeft : "10px"} }); if (button && button.parentNode) this.layout.addToParent(this.loginSpinner, button.parentNode, button); else console.log("missing button/textField!"); }, stopSpinner: function(button) { if (this.loginSpinner && this.loginSpinner.parentNode) this.loginSpinner.parentNode.removeChild(this.loginSpinner) }, logoutButton: function() { return { html: "Log out", autoType : AUTO_TYPE.button, className : "adLoginButton", plain : 1, target : this, listener: "logout" } }, logout: function() { //call API, we might not be able to access cookies through JS. At least shouldn't. this.layout.postAPI({ doLogout : 1}, { target : this }, this.api); }, updateDeleteDiv: function() { var children; switch (this.deleteState) { case this.deleteEnum.start: children = [{ html: "Delete account", autoType : AUTO_TYPE.button, target: this, listener: "deleteAccount" }] break; case this.deleteEnum.warningRevealed: children = [ { text: "This will permanently delete your account, your subscription and all your data. There is no undoing this.\nAt least take the time to export your feeds before deleting your account." }, { type: "h2", html: "Password" }, //should we allow autocomplete? autocomplete : "current-password", { var: "deleteAccountPassword", autoType: AUTO_TYPE.textField, className: "input_field", placeholder : "Password", textFieldType : "password", target : this, }, { html: "Delete account now", autoType: AUTO_TYPE.button, target: this, listener: "deleteAccount" } ] break; case this.deleteEnum.deleted: children = [{ text: "#Thank you for using feeds!\nYour account is permanently deleted."}] break; default: break; } var json = { children: children } var deleteDiv = this.layout.elements.autoDeleteAccountDiv; this.layout.layoutJSON(deleteDiv, children) }, deleteAccount: function() { this.layout.hideAutoError(); switch (this.deleteState) { case this.deleteEnum.start: this.deleteState++ this.updateDeleteDiv() break; case this.deleteEnum.warningRevealed: var passwordField = this.layout.elements.deleteAccountPassword; if (passwordField && passwordField.value && passwordField.value.length > 3) { this.layout.postAPI({ "autoDeleteAccount": passwordField.value }, { target: this, successCallback: "deleteAccountConfirm" }) } else { this.layout.showAutoError("Enter your password to confirm deletion.") } break; default: break; } }, deleteAccountConfirm: function() { this.deleteState++ this.updateDeleteDiv() }, }