adblocker-image-data.js
1 year ago
admin-global.js
1 year ago
admin.js
2 days ago
advertisement.js
3 months ago
conditions.js
1 year ago
dialog-advads-modal.js
1 year ago
termination.js
1 year ago
ui.js
3 years ago
termination.js
275 lines
| 1 | // phpcs:disable Generic.WhiteSpace.ScopeIndent.IncorrectExact -- PHPCS can't handle es5 short functions |
| 2 | function Advads_Termination( element ) { |
| 3 | /** |
| 4 | * Function to reset the changed nodes to default values. |
| 5 | * |
| 6 | * @constructor |
| 7 | */ |
| 8 | function FormValues() { |
| 9 | this.addedNodes = []; |
| 10 | this.removedNodes = []; |
| 11 | }; |
| 12 | |
| 13 | this.initialFormValues = new FormValues(); |
| 14 | this.changedFormValues = new FormValues(); |
| 15 | |
| 16 | const blocklist = [ |
| 17 | 'active_post_lock' |
| 18 | ]; |
| 19 | |
| 20 | this.observers = { |
| 21 | list: [], |
| 22 | |
| 23 | push: item => { |
| 24 | this.observers.list.push( item ); |
| 25 | }, |
| 26 | |
| 27 | disconnect: () => { |
| 28 | this.observers.list.forEach( observer => { |
| 29 | observer.disconnect(); |
| 30 | } ); |
| 31 | this.observers.list = []; |
| 32 | } |
| 33 | }; |
| 34 | |
| 35 | /** |
| 36 | * Set an initial form value. |
| 37 | * Can be used e.g. when a field is updated after an AJAX call. |
| 38 | * |
| 39 | * @param {String} key The key of the value that should be changed in the initial form value array. |
| 40 | * @param {Node} input The input field node. |
| 41 | * |
| 42 | * @returns {void} |
| 43 | */ |
| 44 | this.setInitialValue = ( key, input ) => { |
| 45 | if ( ! input || ! input.value ) { |
| 46 | return; |
| 47 | } |
| 48 | this.initialFormValues[key] = input.value; |
| 49 | }; |
| 50 | |
| 51 | /** |
| 52 | * Collect input values. |
| 53 | * Checkboxes are true/false, unless they are part of a group. |
| 54 | * Radio buttons have a boolean value on the saved value, only the checked one will be collected. |
| 55 | * |
| 56 | * @param {FormValues} object |
| 57 | * @param {Node} input |
| 58 | * @return {FormValues} |
| 59 | */ |
| 60 | const collectInputValue = function ( object, input ) { |
| 61 | /** |
| 62 | * Collect checkbox group values. |
| 63 | * If there are multiple checkboxes with the same `nome` attribute, collect all values for this group. |
| 64 | * |
| 65 | * @param {NodeList} group Iterable of inputs with the same `name` attribute. |
| 66 | * @return {FormValues} |
| 67 | */ |
| 68 | const collectCheckboxGroup = ( group ) => { |
| 69 | |
| 70 | object[group[0].name] = []; |
| 71 | group.forEach( input => { |
| 72 | if ( input.checked ) { |
| 73 | object[input.name].push( input.value ); |
| 74 | } |
| 75 | } ); |
| 76 | |
| 77 | return object; |
| 78 | }; |
| 79 | |
| 80 | if ( input.type === 'checkbox' ) { |
| 81 | const checkboxGroup = element.querySelectorAll( '[name="' + input.name + '"]' ); |
| 82 | if ( checkboxGroup.length > 1 ) { |
| 83 | return collectCheckboxGroup( checkboxGroup, input ); |
| 84 | } |
| 85 | |
| 86 | object[input.name] = input.checked; |
| 87 | |
| 88 | return object; |
| 89 | } |
| 90 | |
| 91 | // if a radio button is not checked, don't collect it. |
| 92 | if ( input.type === 'radio' && ! input.checked ) { |
| 93 | return object; |
| 94 | } |
| 95 | |
| 96 | object[input.name] = input.value; |
| 97 | |
| 98 | return object; |
| 99 | }; |
| 100 | |
| 101 | /** |
| 102 | * Setup a mutationobserver to check for added and removed form fields. |
| 103 | * This especially applies to conditions. |
| 104 | * |
| 105 | * @type {MutationObserver} |
| 106 | */ |
| 107 | const addedRemovedObserver = new MutationObserver( mutations => { |
| 108 | for ( const mutation of mutations ) { |
| 109 | for ( const removedNode of mutation.removedNodes ) { |
| 110 | const nodes = document.createTreeWalker( removedNode, NodeFilter.SHOW_ELEMENT ); |
| 111 | while ( nodes.nextNode() ) { |
| 112 | if ( nodes.currentNode.tagName === 'INPUT' || nodes.currentNode.tagName === 'SELECT' ) { |
| 113 | const index = this.changedFormValues.addedNodes.indexOf( nodes.currentNode.name ); |
| 114 | if ( index > - 1 ) { |
| 115 | this.changedFormValues.addedNodes.splice( index, 1 ); |
| 116 | } else { |
| 117 | this.changedFormValues.removedNodes.push( nodes.currentNode.name ); |
| 118 | } |
| 119 | } |
| 120 | } |
| 121 | } |
| 122 | for ( const addedNode of mutation.addedNodes ) { |
| 123 | if ( addedNode.nodeType === Node.TEXT_NODE ) { |
| 124 | continue; |
| 125 | } |
| 126 | |
| 127 | const nodes = document.createTreeWalker( addedNode, NodeFilter.SHOW_ELEMENT ); |
| 128 | while ( nodes.nextNode() ) { |
| 129 | if ( nodes.currentNode.tagName === 'INPUT' || nodes.currentNode.tagName === 'SELECT' ) { |
| 130 | if ( nodes.currentNode.name === '' ) { |
| 131 | continue; |
| 132 | } |
| 133 | this.changedFormValues.addedNodes.push( nodes.currentNode.name ); |
| 134 | } |
| 135 | } |
| 136 | } |
| 137 | } |
| 138 | } ); |
| 139 | |
| 140 | // attach the mutation observer to the passed element. |
| 141 | addedRemovedObserver.observe( element, {childList: true, subtree: true} ); |
| 142 | this.observers.push( addedRemovedObserver ); |
| 143 | |
| 144 | /** |
| 145 | * Check if there are inputs that have been changed and if their value is different. |
| 146 | * |
| 147 | * @param {Object} reference The initial values when the modal loaded, indexed by name attribute. |
| 148 | * @param {Object} changed The input values that were changed, indexed by name. |
| 149 | * |
| 150 | * @return {boolean} |
| 151 | */ |
| 152 | this.hasChanged = ( reference, changed ) => { |
| 153 | for ( const name in changed ) { |
| 154 | if ( ! reference.hasOwnProperty( name ) || reference[name].toString() !== changed[name].toString() ) { |
| 155 | return true; |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | return false; |
| 160 | }; |
| 161 | |
| 162 | /** |
| 163 | * If the modal is associated with a form and any values have changed, ask for confirmation to navigate away. |
| 164 | * Returns true if the user agrees with termination, false otherwise. |
| 165 | * |
| 166 | * @param {boolean} reload Whether to reload the page on added and removed nodes (needed for the modal). Default false. |
| 167 | * |
| 168 | * @return {boolean} |
| 169 | */ |
| 170 | this.terminationNotice = ( reload = false ) => { |
| 171 | if ( ! this.hasChanged( this.initialFormValues, this.changedFormValues ) ) { |
| 172 | return true; |
| 173 | } |
| 174 | |
| 175 | // ask user for confirmation. |
| 176 | if ( window.confirm( window.advadstxt.confirmation ) ) { |
| 177 | // if we have added or removed nodes, we might need to reload the page. |
| 178 | if ( this.changedFormValues.addedNodes.length || this.changedFormValues.removedNodes.length ) { |
| 179 | if ( reload ) { |
| 180 | window.location.reload(); |
| 181 | } |
| 182 | return true; |
| 183 | } |
| 184 | |
| 185 | // otherwise, we'll replace the values with the previous values. |
| 186 | for ( const name in this.changedFormValues ) { |
| 187 | const input = element.querySelector( '[name="' + name + '"]' ); |
| 188 | if ( input === null ) { |
| 189 | continue; |
| 190 | } |
| 191 | |
| 192 | if ( input.type === 'checkbox' ) { |
| 193 | input.checked = this.initialFormValues[name]; |
| 194 | } else if ( input.type === 'radio' ) { |
| 195 | let value = (this.initialFormValues[name] !== null && this.initialFormValues[name] !== undefined) ? this.initialFormValues[name] : input.value; |
| 196 | element.querySelector( '[name="' + name + '"][value="' + value + '"]' ).checked = true; |
| 197 | } else { |
| 198 | input.value = this.initialFormValues[name]; |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | return true; |
| 203 | } |
| 204 | |
| 205 | return false; |
| 206 | }; |
| 207 | |
| 208 | /** |
| 209 | * Set the initial values to the current ones, then reset the changed form values |
| 210 | */ |
| 211 | this.resetInitialValues = () => { |
| 212 | if ( this.changedFormValues.addedNodes.length ) { |
| 213 | for ( const name in this.changedFormValues.addedNodes ) { |
| 214 | this.initialFormValues[name] = this.changedFormValues.addedNodes[name]; |
| 215 | } |
| 216 | } |
| 217 | if ( this.changedFormValues.removedNodes.length ) { |
| 218 | for ( const name in this.changedFormValues.removedNodes ) { |
| 219 | if ( this.initialFormValues[name] !== undefined ) { |
| 220 | delete ( this.initialFormValues[name] ); |
| 221 | } |
| 222 | } |
| 223 | } |
| 224 | for ( const name in this.changedFormValues ) { |
| 225 | if ( 'removedNodes' === name || 'addedNodes' === name ) { |
| 226 | continue; |
| 227 | } |
| 228 | if ( this.initialFormValues[name] !== undefined ) { |
| 229 | this.initialFormValues[name] = this.changedFormValues[name]; |
| 230 | } |
| 231 | } |
| 232 | this.changedFormValues = new FormValues(); |
| 233 | }; |
| 234 | |
| 235 | /** |
| 236 | * Collect inputs in this modal and save their initial and changed values (if any). |
| 237 | */ |
| 238 | this.collectValues = () => { |
| 239 | const isDialog = element.tagName === 'DIALOG'; |
| 240 | |
| 241 | element.querySelectorAll( 'input, select, textarea' ).forEach( input => { |
| 242 | if ( ! input.name.length || blocklist.includes( input.id ) || blocklist.includes( input.name ) ) { |
| 243 | return; |
| 244 | } |
| 245 | |
| 246 | // if the element itself is not a dialog but the input is within a dialog, ignore it. This accounts for split forms, e.g. the placements page where some inputs are hidden in a modal dialog. |
| 247 | if ( ! isDialog && input.closest( 'dialog' ) ) { |
| 248 | return; |
| 249 | } |
| 250 | |
| 251 | this.initialFormValues = collectInputValue( this.initialFormValues, input ); |
| 252 | |
| 253 | // if the input is `hidden` no change event gets triggered. Use MutationObservers to check for changes in the value attribute. |
| 254 | if ( input.type === 'hidden' ) { |
| 255 | const hiddenObserver = new MutationObserver( function ( mutations, observer ) { |
| 256 | mutations.forEach( mutation => { |
| 257 | if ( mutation.attributeName === 'value' ) { |
| 258 | mutation.target.dispatchEvent( new Event( 'input' ) ); |
| 259 | } |
| 260 | } ); |
| 261 | } ); |
| 262 | hiddenObserver.observe( element, { |
| 263 | attributes: true, |
| 264 | subtree: true |
| 265 | } ); |
| 266 | this.observers.push( hiddenObserver ); |
| 267 | } |
| 268 | |
| 269 | input.addEventListener( 'input', event => { |
| 270 | this.changedFormValues = collectInputValue( this.changedFormValues, input ); |
| 271 | } ); |
| 272 | } ); |
| 273 | }; |
| 274 | }; |
| 275 |