function validateUrl(value) {
    if ($('#node-input-url').is(':visible')) {
        // Update the DEPLOYED instances list (also updates this.isDeployed) - not needed on Editor load
        updateDeployedInstances(this)
    }

    // this.urlValid = false
    this.urlErrors = {}

    this.urlDeployedChanged = uibuilder.deployedUibInstances[this.id] !== value //  || (this.oldUrl !== undefined && this.url !== this.oldUrl)
    this.urlChanged = (this.url !== value)

    let f = Object.values(uibuilder.deployedUibInstances).indexOf(value)
    this.urlDeployedDup = ( f > -1 && Object.keys(uibuilder.deployedUibInstances)[f] !== this.id )
    f = Object.values(editorInstances).indexOf(value)
    this.urlEditorDup = ( f > -1 && Object.keys(editorInstances)[f] !== this.id )

    // If value is undefined, node hasn't been configured yet - we assume empty url which is invalid
    if ( value === undefined ) {
        this.urlErrors.config = 'Not yet configured, valid URL needed'
    }

    // Must be >0 chars
    if ( value === undefined || value === '' ) {
        this.urlErrors.none = 'Must not be empty'
    } else { // These will fail for empty value
        // Max 20 chars
        if ( value.length > 20 ) {
            this.urlErrors.len = 'Too long, must be <= 20 chars'
        }
        // Cannot contain ..
        if ( value.indexOf('..') !== -1 ) {
            this.urlErrors.dbldot = 'Cannot contain <code>..</code> (double-dot)'
        }
        // Cannot contain / or \
        if ( value.indexOf('/') !== -1 ) {
            this.urlErrors.fslash = 'Cannot contain <code>/</code>'
        }
        if ( value.indexOf('\\') !== -1 ) {
            this.urlErrors.bslash = 'Cannot contain <code>\\</code>'
        }
        // Cannot contain spaces
        if ( value.indexOf(' ') !== -1 ) this.urlErrors.sp = 'Cannot contain spaces'
        // Cannot start with _ or .
        if ( value.substring(0, 1) === '_' ) this.urlErrors.strtU = 'Cannot start with <code>_</code> (underscore)'
        if ( value.substring(0, 1) === '.' ) this.urlErrors.strtDot = 'Cannot start with <code>.</code> (dot)'
        // Cannot be 'templates' as this is a reserved value (for v2)
        if ( value.toLowerCase().substring(0, 9) === 'templates' ) this.urlErrors.templ = 'Cannot be "templates"'
        // Cannot be 'common' as this is a reserved value
        if ( value.toLowerCase().substring(0, 9) === 'common' ) this.urlErrors.templ = 'Cannot be "common"'
        // Must not be `uibuilder` (breaking change in v5)
        if ( value.toLowerCase() === 'uibuilder' ) this.urlErrors.uibname = 'Cannot be "uibuilder" (since v5)'
    }

    // TODO ?MAYBE? Notify's shouldn't be here - only needed if "Done" (or event change)

    // Check whether the url is already in use in another deployed uib node
    if ( this.urlDeployedDup === true ) {
        // RED.notify(`<b>ERROR</b>: <p>The chosen URL (${value}) is already in use.<br>It must be changed before you can save/commit</p>`, {type: 'error'})
        this.urlErrors.dup = 'Cannot be a URL already deployed'
    }

    // Check whether the url is already in use in another undeployed uib node
    if ( this.urlEditorDup === true ) {
        // RED.notify(`<b>ERROR</b>: <p>The chosen URL (${value}) is already in use in another undeployed uib node.<br>It must be changed before you can save/commit</p>`, {type: 'error'})
        this.urlErrors.dup = 'Cannot be a URL already in use'
    }

    // Check whether the folder already exists - if it does, give a warning & adopt it
    if ( value !== undefined && value !== '') {
        this.folderExists = queryFolderExists(value)

        /** If the folder already exists but not in another node */
        if ( this.urlEditorDup === false && this.folderExists === true && this.urlChanged === true ) {
            log('>> folder already exists >>', this.url, this.id)
            RED.notify(`<b>WARNING</b>: <p>The folder for the chosen URL (${value}) is already exists.<br>It will be adopted by this node.</p>`, { type: 'warning', })
        }

        if ( this.folderExists === false ) {
            this.urlErrors.fldNotExist = 'URL does not yet exist. You must Deploy first.<br>If changing a deployed URL, the folder will be renamed on Deploy'
        }
    }

    // Warn user when changing URL. NOTE: Set/reset old url in the onsave function not here
    if ( this.isDeployed && this.deployedUrlChanged === true ) {
        log('[uib] >> deployed url changed >> this.url:', this.url, ', this.oldUrl:', this.oldUrl, this.id)
        this.urlErrors.warnChange = `Renaming from ${this.url} to ${value}. <b>MUST</b> redeploy now`
        RED.notify(`<b>NOTE</b>: <p>You are renaming the url from ${this.url} to ${value}.<br>You <b>MUST</b> redeploy before doing anything else.</p>`, { type: 'warning', })
    }

    // Process panel display for valid/invalid URL - NB repeated once in oneditprepare cause 1st time this is called, the panel doesn't yet exist
    if ( Object.keys(this.urlErrors).length > 0) {
        // Changed URL not valid: turn off other edits, show errors
        this.urlValid = false
        enableEdit(this.urlErrors, false)
        this.okToGo = false
    } else {
        // Changed URL valid: turn on other edits
        this.urlValid = true
        enableEdit(this.urlErrors, true)
        this.okToGo = true
    }

    // debugUrl(this, value)

    // Add exception if the only error is that the fldr doesn't yet exist
    if ( Object.keys(this.urlErrors).length === 1 && this.urlErrors.fldNotExist ) {
        this.okToGo = true
        return true
    }

    return this.urlValid
}
Live URL Validation Function: Validate the url property
Max 20 chars, can't contain any of ['..', ]
@param {string} value The url value to validate
@returns {boolean} true = valid
@this {*}