Browsing all articles from September, 2026
Sep
30

Property sales fall as house price growth slows

Author admin    Category Uncategorized     Tags

Property118

Property sales fall as house price growth slows

Higher mortgage rates have helped drive a 9% annual fall in sales agreed across the UK, according to Zoopla’s latest House Price Index.

Also, a quarter of homes listed for sale in September have already been on the market within the past year.

The index reveals that annual growth in property prices has slowed to 0.8%, the lowest since July 2024.

Average mortgage rates have reached 5.2%, their highest in three years, adding £150 a month to the average homebuyer’s repayments compared with the start of the year.

Pricing homes for sale

Zoopla’s executive director, Richard Donnell, said: “While key measures of housing market activity are lower than last year, there is still plenty of demand for homes.

“Buyers are simply more cautious and selective about what they view and offer.”

He added: “Sellers who factor in local market conditions and seek detailed advice from their local estate agents on how to set the asking price, can still find a buyer relatively quickly.

“Getting the right price from the outset is essential.”

More homes for sale

The number of homes for sale is 5% higher than a year ago, and six in 10 homes returning to the market have a lower asking price, with relistings most common among flats and larger houses.

In London, a third of flats coming onto the market have been advertised before, compared with fewer than one in 10 in Scotland.

Around three-quarters of Scottish homes find a buyer within three months; in London, just three in 10 do.

Flat prices keep falling

Flat prices have fallen for 15 consecutive months, while the average UK home now costs £273,000.

Scotland and the North East are among the exceptions, recording annual increases in flat values while prices for this property type fall across most regions.

More affordable two- and three-bedroom houses generally attract stronger demand, according to Zoopla, while flats and larger family homes face tougher conditions, particularly in southern England.

The property website expects annual house price growth to slow to around 0.5% by December and forecasts 1.1 million sales during 2026, compared with 1.2 million last year.

The post Property sales fall as house price growth slows appeared first on Property118.

View Full Article: Property sales fall as house price growth slows

Sep
29

Andy Burnham pledges crackdown on poor-quality PRS homes

Author admin    Category Uncategorized     Tags

Property118

Andy Burnham pledges crackdown on poor-quality PRS homes

The Prime Minister has warned landlords who rent out the “worst-quality homes” will be given warnings and could ultimately face having their properties taken over by councils.

Speaking at the Labour Party conference, Andy Burnham also said he would reform Right to Buy and tackle the empty homes crisis.

The comments come as the government announces plans to strengthen leaseholders’ rights, including tighter regulation of property agents and a cap on fees.

Rented homes in a poor state of repair

Mr Burnham claimed that too many homes in the private rented sector are in a poor state of repair.

Addressing the Labour Party conference, Mr Burnham said: “You will find rented homes in a poor state of repair on so many British streets. Homes which harm the health of the people living inside them and drag down the streets around them.

“The worst of it is often owned by absent landlords, who are paid through the benefits system but refuse to reinvest in the upkeep of their properties.

“It is time to call an end to this. Landlords who rent out the worst-quality homes will be given warnings to improve them. If they refuse, we will make it much easier for councils to acquire those homes, and we will legislate if necessary.”

Mr Burnham added: “We don’t let people sell food that damages people’s health, so why should we allow it with homes? From now on, we won’t. This is what I mean by taking back control with our communities.”

Council housebuilding revolution

Mr Burnham added that the government would reform Right to Buy and launch a council housebuilding revolution.

He said: “I am specific about council homes because we need to restore stronger democratic control over our housing stock.

“We will help councils build again and make it affordable by building on unused public land. We will reform Right to Buy, not taking the right away from anyone in an existing home, but removing it from the new homes we build, so they are there for generations to come.”

The news comes as the government have announced councils in England will be able to take control of homes left empty for six months under government plans to cut the current two-year threshold.

Councils will also no longer need to provide evidence relating to anti-social behaviour or community support when seeking an order through the Residential Property Tribunal.

Industry reaction

Timothy Douglas, head of policy and campaigns at Propertymark, said further clarity is needed on how the proposals would work in practice.

He said: “Addressing poorly maintained properties should remain a key concern, with local authorities across England working closely with residents, landlords and property agents to understand why properties may fall below the required standard.

“Every case will have different circumstances, so any proposals must be proportionate and recognise the individual circumstances of the property and those living in it.

“Further clarity is needed on how the proposals would work in practice, including carrying out remedial work with a tenant in situ, the responsibilities of different parties, and how properties would be managed if they passed into local authority control or ownership.

“With the new ‘Register Your Rental Property Service’ being implemented for landlords from December, councils will have greater access to information to support targeted, intelligence-led enforcement. The focus should be on making better use of existing tools rather than simply adding another layer of legislation and potentially damaging relationships between good landlords and government.

“Any new proposals should deliver a fair, proportionate and tailored approach that achieves sustainable, long-term outcomes.”

The post Andy Burnham pledges crackdown on poor-quality PRS homes appeared first on Property118.

View Full Article: Andy Burnham pledges crackdown on poor-quality PRS homes

Sep
29

Right to Rent changes for England’s landlords from October

Author admin    Category Uncategorized     Tags

Property118

Right to Rent changes for England’s landlords from October

Landlords in England who use digital verification services for Right to Rent checks must choose registered providers under a revised code taking effect on 1 October.

However, tenants cannot be disadvantaged for choosing acceptable paper documents instead.

The revised Home Office codes cover checking procedures and avoiding unlawful discrimination.

They update guidance on digital evidence, facial recognition and the treatment of applicants whose immigration status requires further checks.

Fair treatment rules

The Home Office’s discrimination code states: “Individuals must not be treated less favourably because a different right to rent checking route is required.”

It also warns landlords against favouring applicants simply because their status is easier to verify.

Those with a time-limited right to rent must receive equal treatment once they have demonstrated their entitlement, even where follow-up checks are necessary.

Nor should landlords insist on digital evidence from people who cannot use the Home Office online service.

Digital checking requirements

A digital verification provider must appear on the government register with confirmation that it can conduct right to rent checks.

Using such a provider remains optional, with other permitted checking methods still available.

Where facial recognition fails, landlords must give the applicant a reasonable opportunity to verify their identity.

For tenants with eVisas, landlords must use the Home Office online checking service, although the Landlord Checking Service can help where technical problems prevent verification.

Avoiding discrimination claims

Checks should be applied consistently, including to applicants whom landlords believe are British.

The code warns against assumptions based on someone’s accent, nationality, colour or ethnic origins.

Subject to business requirements, landlords should, where possible, keep an accommodation offer open for a reasonable period while an applicant obtains evidence of their right to rent.

Anyone alleging unlawful discrimination by a landlord or prospective landlord can bring a complaint before a county court.

The post Right to Rent changes for England’s landlords from October appeared first on Property118.

View Full Article: Right to Rent changes for England’s landlords from October

Sep
29

Should we understand why a property is empty before introducing more damaging legislation?

Author admin    Category Uncategorized     Tags

Property118

Should we understand why a property is empty before introducing more damaging legislation?

The Renters’ Rights Act has introduced new legislation to ensure some property is kept empty for a year. If a tenant is issued a section 8 for property sale purposes and the landlord subsequently changes their mind about the sale, the property cannot be re-let for a year.

Meanwhile, the government is contemplating legislation to take control of houses and re-let them, should they have been empty for six months.

Maybe it is too much to hope for, but could someone undertake a sensible project and first understand why property is empty before introducing more damaging statute?

Best wishes,

Paul

The post Should we understand why a property is empty before introducing more damaging legislation? appeared first on Property118.

View Full Article: Should we understand why a property is empty before introducing more damaging legislation?

Sep
29

Should we understand why a property is empty before introducing more damaging legislation?

Author admin    Category Uncategorized     Tags

Property118

Should we understand why a property is empty before introducing more damaging legislation?

The Renters’ Rights Act has introduced new legislation to ensure some property is kept empty for a year. If a tenant is issued a section 8 for property sale purposes and the landlord subsequently changes their mind about the sale, the property cannot be re-let for a year.

Meanwhile, the government is contemplating legislation to take control of houses and re-let them, should they have been empty for six months.

Maybe it is too much to hope for, but could someone undertake a sensible project and first understand why property is empty before introducing more damaging statute?

Best wishes,

Paul

The post Should we understand why a property is empty before introducing more damaging legislation? appeared first on Property118.

View Full Article: Should we understand why a property is empty before introducing more damaging legislation?

Sep
28

PRS Database tipping point – Turning landlord problems into cash in the bank

Author admin    Category Uncategorized     Tags

Property118

PRS Database tipping point – Turning landlord problems into cash in the bank

News of the PRS Database has been the last straw for some landlords.

Every landlord in England will need to sign up and register each property for £65 per property every year.

For landlords already dealing with the Renters’ Rights Act, licensing, tax changes and increasing compliance, it is another requirement to add to the list.

And for some, it may be the point where they start thinking seriously about selling up.

If that is you, this is where Landlord Sales Agency comes in.

You might want to sell but have a compliance problem to sort out first. You might have tenants in place and not know what to do with them. You might have an HMO licensing issue, a property that needs work or simply a portfolio that you want to sell without spending months dealing with estate agents, viewings and negotiations.

Whatever is holding up your decision to sell, speak to us before assuming you need to solve it yourself.

A recent Lincoln HMO is a good example of what can be achieved when a sale hits an unexpected problem.

The seller had planned a nine-bed HMO, but due to a misunderstanding regarding the layout, the council only issued a six-bed licence.

Two lenders then declined the deal.

For many sellers, that could have meant the sale falling apart and going back to square one.

We took a different approach.

Working with our specialist HMO broker, surveyor, lender contacts and the council, we secured an upgraded HMO licence to secure finance based on the future nine-bed configuration and the accepted offer price, enabling the sale to proceed once the required works were completed and the licensing position resolved.

The problem did not disappear. We simply found a practical way through it.

That is important because landlords do not always come to us with a perfect, ready-to-sell property.

Equally, you do not need to have a problem property to use us.

Whether you have one straightforward rental property, a group of well-performing tenanted houses, or a large portfolio with a mixture of property types, we can help.

We know landlords want cash in the bank fast, and selling can become a long-drawn-out process, particularly when going down the traditional estate-agent route.

That is why we don’t limit ourselves to one type of buyer or one way of selling.

Depending on the property, we can look at investors, other landlords, owner-occupiers and, where appropriate, even help an existing tenant buy the home themselves.

If your tenants are the reason you think you cannot sell yet, speak to us before starting an eviction.

Where a property requires vacant possession, we can work directly with existing tenants to try to find a solution. That can include negotiating rents where appropriate to avoid the need for eviction, helping tenants relocate and, in some circumstances, providing financial help towards securing somewhere else to live.

Alternatively, there may be no need for the tenant to leave at all. We can sell with tenants remaining in place and rent continuing to be collected until completion.

For suitable tenants who would like to buy their home, we can also explore whether we can help them become the buyer.

The point is that selling a rental property does not always have to start with an eviction, months of waiting or solving every compliance issue yourself.

No matter whether you have one property or an entire portfolio, the first step is finding out what your options actually are.

We can sell individual properties, entire portfolios or partial portfolios in bulk, allowing landlords to keep the properties they still want while releasing cash from those they no longer do.

If the PRS Database is the latest requirement that has made you question whether now is the time to sell, don’t wait until every problem has been solved before speaking to us.

You may have more options than you think.

For a free, no-obligation conversation about your property or portfolio and what we could do to get cash in the bank, contact Landlord Sales Agency today.

(function(){var el=document.getElementById(“ts-607776b5-3f7b-431c-b5cc-fc304603fee3″);if(!el)return;var b=document.body,h=document.documentElement;var dark=b.classList.contains(“dark-mode”)||b.classList.contains(“dark”)||b.classList.contains(“night-mode”)||h.classList.contains(“dark”);if(!dark){var bg=window.getComputedStyle(b).backgroundColor,m=bg.match(/d+/g);if(m)dark=(m[0]*0.299+m[1]*0.587+m[2]*0.114)<128;}el.setAttribute("data-theme",dark?"dark":"light");if(window.turnstile&&el.childElementCount===0){try{window.turnstile.render(el,{sitekey:el.getAttribute("data-sitekey"),theme:el.getAttribute("data-theme")});}catch(e){}}})();

(function(){
var uid = “crm-form-ba09a29b”;
var form = document.getElementById(uid + ‘-form’);
var wrap = document.getElementById(uid);
var msg = wrap.querySelector(‘.crm-message’);
var totalPages = 1;
var curPage = 0;

// ── Name casing ──────────────────────────────────────────────────────
// Tidy a name box as the visitor leaves it, so they are not staring at
// “JOHN SMITH” in their own form. Deliberately cosmetic: the API runs
// the same normalisation on submit and is the authority on what gets
// stored, so a disagreement here is harmless. Kept in step with
// packages/shared/src/names.ts.
var NAME_PARTICLES = ‘ van von de del della der den di da dal du la le el bin ibn al dos das do ter ten ‘;
var NAME_MACS = ‘ macdonald macintyre macleod macarthur macgregor macneil macpherson macrae mackinnon macqueen ‘;
function nameUp(s){ return s.replace(/[a-z]/i, function(c){ return c.toUpperCase(); }); }
function isParticle(w){ return NAME_PARTICLES.indexOf(‘ ‘ + w.toLowerCase() + ‘ ‘) >= 0; }
function nameRecase(atom){
if (!/[a-z]/i.test(atom)) return atom;
var dot = atom.charAt(atom.length – 1) === ‘.’ ? ‘.’ : ”;
var core = dot ? atom.slice(0, -1) : atom;
var lower = core.toLowerCase();
if (/^(?:II|III|IV|VI|VII|VIII|IX|XI|XII)$/.test(core.toUpperCase())) return core.toUpperCase() + dot;
if (lower === ‘jr’ || lower === ‘sr’) return nameUp(lower) + dot;
if (core.length === 1) return core.toUpperCase() + dot;
var mc = /^mc([a-z]{2,})$/.exec(lower);
if (mc) return ‘Mc’ + nameUp(mc[1]) + dot;
if (NAME_MACS.indexOf(‘ ‘ + lower + ‘ ‘) >= 0) return ‘Mac’ + nameUp(lower.slice(3)) + dot;
return nameUp(lower) + dot;
}
function nameWellFormed(atom){
// “Mccoll” has its leading capital but still wants McColl. Safe only
// because “Mc” is unambiguous — “Mac” deliberately has no such rule.
if (/^Mc[a-z]{2,}$/.test(atom)) return false;
return /^(?:[A-Z][a-z]*|[A-Z][a-z]+[A-Z][A-Za-z]*|O[A-Z][a-z]+|[A-Z]{1,3}|[IVX]+)$/.test(atom);
}
function tidyName(value){
var v = (value || ”).replace(/s+/g, ‘ ‘).replace(/^ | $/g, ”);
if (!v) return value;
if (/[@d,]|&[a-z]+;|&#|https?:|www./i.test(v)) return value; // not a name
var words = v.split(‘ ‘);
var deliberate = words.every(function(w){
// A lone particle is not a particle — it is someone’s name (Di, Al).
if (words.length > 1 && isParticle(w)) return true;
return w.split(/[-‘u2019.]/).filter(function(seg){ return /[a-z]/i.test(seg); })
.every(function(seg){ return nameWellFormed(seg); });
});
if (deliberate) return v;
return words.map(function(w, i){
if (i > 0 && isParticle(w)) return w.toLowerCase();
return w.split(/([-‘u2019.])/).map(function(seg){
return /^[-‘u2019.]$/.test(seg) ? seg : nameRecase(seg);
}).join(”);
}).join(‘ ‘);
}
// ── Thousand separators ──────────────────────────────────────────────
// Opt-in per field (data-thousands). Mirrors formatThousands() and the
// caret helpers in the CRM’s packages/shared/src/numbers.ts. The CRM
// strips the separators again on submit, so storage is unaffected.
function groupThousands(value){
var raw = String(value == null ? ” : value).replace(/,/g, ”);
if (!raw) return ”;
var neg = raw.charAt(0) === ‘-‘;
var body = neg ? raw.slice(1) : raw;
var dot = body.indexOf(‘.’);
var digits = function(x){ return x.replace(/[^0-9]/g, ”); };
var whole = digits(dot === -1 ? body : body.slice(0, dot));
var frac = dot === -1 ? null : digits(body.slice(dot + 1));
if (!whole && frac === null) return neg ? ‘-‘ : ”;
var grouped = whole.replace(/B(?=(d{3})+(?!d))/g, ‘,’);
return (neg ? ‘-‘ : ”) + grouped + (frac === null ? ” : ‘.’ + frac);
}
// Reposition by digit count, not character index: commas appear and
// vanish to the left of the caret, and counting characters would throw
// it to the end of the field on every keystroke.
function caretAfterGrouping(formatted, digitsBefore){
if (digitsBefore <= 0) return 0;
var seen = 0;
for (var i = 0; i parseFloat(cmp);
case ‘lessthan': return parseFloat(val) < parseFloat(cmp);
case 'contains': return val.indexOf(cmp) !== -1;
case 'startswith': return val.indexOf(cmp) === 0;
case 'endswith': return val.slice(-cmp.length) === cmp;
default: return false; // fail closed — mirror the CRM shared matcher
}
}

function applyConditionals() {
Object.keys(condMap).forEach(function(fieldId) {
var cond = condMap[fieldId];
var rules = cond.rules || [];
var match = cond.logicType === 'any'
? rules.some(evalRule)
: rules.every(evalRule);
var show = cond.actionType === 'show' ? match : !match;
var wrapper = form.querySelector('[data-field-id="' + fieldId + '"]');
if (!wrapper) {
var el = form.querySelector('[name="' + fieldId + '"], [name="' + fieldId + '[]"]');
if (el) wrapper = el.closest('.crm-field, .crm-half');
}
if (wrapper) wrapper.style.display = show ? '' : 'none';
});
}

form.addEventListener('change', applyConditionals);
form.addEventListener('input', applyConditionals);
applyConditionals();

// ── Required-field validation (Next + Submit) ────────────────────────
// Validate from the form's field config (id + type), NOT the [required]
// HTML attribute: a checkbox group can't carry a meaningful `required`
// (native means "tick every box"), so attribute checks skip it — which is
// why empty checkbox questions slipped past Next straight to submit.
var crmRequired = [{"id":"c364951b-ecb0-4a12-bfa4-9dbe564310e8","type":"name","label":"Name"},{"id":"910107df-b8a8-4541-879f-541cef9449e4","type":"email","label":"Email"},{"id":"3b317ed9-d327-4fe2-b92e-67efede67e6d","type":"phone","label":"Phone"},{"id":"c6fae2f0-c335-452b-aa03-2cc066163d71","type":"textarea","label":"Please give us details of how we can help and the properties in question"},{"id":"34c27d9f-b3d1-4a6d-b720-24000e9fa468","type":"checkbox","label":"Privacy Policy"}];
function crmWrapper(id) {
var w = form.querySelector('[data-field-id="' + id + '"]');
if (!w) { var el = form.querySelector('[name="' + id + '"], [name="' + id + '[]"]'); if (el) w = el.closest('.crm-field, .crm-half'); }
return w;
}
function crmFilled(fld) {
// The [name^="id["] arm catches composite fields. A name renders as id[first] /
// id[last] and an address as id[street] / id[city] / …, none of which match
// the two exact-name selectors – so els came back empty, the !els.length guard
// below returned true, and the field counted as filled. With novalidate on the
// form the browser's own required check is off too, so a required Name field
// could be left completely blank and both Next and Submit went through.
var els = form.querySelectorAll('[name="' + fld.id + '"], [name="' + fld.id + '[]"], [name^="' + fld.id + '["]');
if (!els.length) return true;
// Decide on what is actually RENDERED, not the declared type. A `product`
// field is choices (radio/checkbox) for a priced list but a single amount
// box for a user-defined price — and testing that box for .checked, which
// a text input never has, meant a required product field could never be
// satisfied. An invoice or "Request For Payment" form was unsubmittable:
// the payer typed an amount, hit Submit, and got "Please select at least
// one option." under the box, every time.
var checkable = Array.prototype.filter.call(els, function (el) {
return el.type === 'checkbox' || el.type === 'radio';
});
if (checkable.length) { return checkable.some(function (el) { return el.checked; }); }
if (['checkbox','radio','consent'].indexOf(fld.type) !== -1) { return false; }
// Test the sub-inputs the renderer actually marked required – a name field puts
// `required` on both first and last, an address field on none of its six – and
// fall back to the first input when nothing is marked, which is the existing
// single-input behaviour unchanged.
var reqEls = Array.prototype.filter.call(els, function (el) { return el.hasAttribute('required'); });
// Repeaters name their sub-inputs '[][]’, so the prefix arm above
// collects EVERY rendered row. Demanding all of them be filled means one blank
// extra row – added by mistake, or rendered by settings.minRows – blocks Next and
// Submit with a generic message on the wrapper. Only the first row is required.
var firstRow = form.querySelector(‘[data-field-id=”‘ + fld.id + ‘”] .crm-repeater-row’);
if (firstRow) {
reqEls = Array.prototype.filter.call(reqEls, function (el) { return firstRow.contains(el); });
}
var toTest = reqEls.length ? reqEls : [els[0]];
return Array.prototype.every.call(toTest, function (el) { return (el.value || ”).trim() !== ”; });
}
function crmFirstInvalid(pageIdx) {
for (var i = 0; i < crmRequired.length; i++) {
var fld = crmRequired[i]; var w = crmWrapper(fld.id);
if (!w) continue;
if (w.style.display === 'none') continue;
if (pageIdx != null) { var pd = w.closest('[data-page]'); if (!pd || parseInt(pd.getAttribute('data-page')) !== pageIdx) continue; }
if (!crmFilled(fld)) return fld;
}
return null;
}
// Shows the error in a red box UNDER the field, matching the form's own
// .crm-message error styling, and scrolls to it.
function crmMarkError(w, text) {
if (!w) return;
var e = w.querySelector('.crm-inline-error');
if (!e) { e = document.createElement('div'); e.className = 'crm-inline-error'; w.appendChild(e); }
e.style.cssText = 'background:#fee2e2;color:#991b1b;padding:.55rem .75rem;border-radius:4px;margin-top:.4rem;font-size:.9rem';
e.textContent = text || 'Please answer this before continuing.';
}
function crmClearError(w) { if (!w) return; var e = w.querySelector('.crm-inline-error'); if (e) e.parentNode.removeChild(e); }
function crmReportInvalid(fld) {
var w = crmWrapper(fld.id);
// Match the message to what the visitor is actually looking at — an amount
// box should not be told to "select an option".
var els = form.querySelectorAll('[name="' + fld.id + '"], [name="' + fld.id + '[]"]');
var hasChoices = Array.prototype.some.call(els, function (el) {
return el.type === 'checkbox' || el.type === 'radio';
});
var t;
if (fld.type === 'product' && !hasChoices) { t = 'Please enter an amount.'; }
else if (['checkbox','product'].indexOf(fld.type) !== -1) { t = 'Please select at least one option.'; }
else if (fld.type === 'radio') { t = 'Please choose an option.'; }
else { t = 'Please fill this in.'; }
crmMarkError(w, t);
if (w && w.scrollIntoView) w.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
form.addEventListener('change', function(e){ var w = e.target && e.target.closest ? e.target.closest('.crm-field, .crm-half, [data-field-id]') : null; if (w) crmClearError(w); });
form.addEventListener('input', function(e){ var w = e.target && e.target.closest ? e.target.closest('.crm-field, .crm-half, [data-field-id]') : null; if (w) crmClearError(w); });

// ── Multi-page navigation ────────────────────────────────────────────

// ── Submit ────────────────────────────────────────────────────────────
var submitBtn = form.querySelector('button[type=submit]');
var btnText = submitBtn ? submitBtn.textContent : 'Submit';

var crmApiUrl = "https://crm-api.property118.com";

function collectFormData() {
var data = {};
for (var j = 0; j < form.elements.length; j++) {
var el = form.elements[j];
if (!el.name) continue;
if (el.type === 'file') continue; // handled by uploadFiles()
if (el.type === 'radio' && !el.checked) continue;
if (el.type === 'checkbox') {
if (!el.checked) continue;
var k = el.name.replace('[]','');
data[k] = data[k] ? data[k].concat([el.value]) : [el.value];
} else {
data[el.name] = el.value;
}
}
// Repeater fields: sub-inputs are named "[][]”.
// Group those flat keys into an array of row objects under the repeater
// id. Plain fields, checkbox “[]” arrays and single-bracket composites
// (name[first], address[city]) don’t match the double-bracket pattern, so
// ordinary submissions are unchanged.
var _repRe = /^(.+?)[(d+)][(.+)]$/;
var _repIds = {};
Object.keys( data ).forEach( function ( key ) {
var m = key.match( _repRe );
if ( ! m ) { return; }
var rid = m[1], row = parseInt( m[2], 10 ), sub = m[3];
_repIds[ rid ] = true;
if ( ! Array.isArray( data[ rid ] ) ) { data[ rid ] = []; }
if ( ! data[ rid ][ row ] || typeof data[ rid ][ row ] !== ‘object’ ) { data[ rid ][ row ] = {}; }
data[ rid ][ row ][ sub ] = data[ key ];
delete data[ key ];
} );
Object.keys( _repIds ).forEach( function ( rid ) {
if ( Array.isArray( data[ rid ] ) ) {
data[ rid ] = data[ rid ].filter( function ( r ) { return r && typeof r === ‘object'; } );
}
} );
// GF auto-substituted {user_agent} / {referer} on hidden fields
// at render time. We do the equivalent right before submit so
// fields whose default value carries these placeholders
// resolve to the browser’s actual values rather than being
// stored as literal “{user_agent}” / “{referer}” strings.
// The last ARTICLE the visitor read (set client-side on post views by
// P118_Article_Views). Used to attribute the enquiry to the article
// even after navigating away or with a stripped/absent referer.
var lastPost = ”;
try {
var lpm = document.cookie.match(/(?:^|;s*)p118_last_post=([^;]+)/);
if (lpm) lastPost = decodeURIComponent(lpm[1]);
} catch (e) {}
var subs = {
‘{user_agent}': navigator.userAgent || ”,
// Prefer the last article read; fall back to the (lossy) HTTP referer.
‘{referer}': lastPost || document.referrer || ”,
‘{last_post}': lastPost || ”,
‘{embed_url}': window.location.href || ”,
};
for (var name in data) {
if (!data.hasOwnProperty(name)) continue;
var v = data[name];
if (typeof v !== ‘string’) continue;
for (var tag in subs) {
if (v.indexOf(tag) !== -1) v = v.split(tag).join(subs[tag]);
}
data[name] = v;
}
// Agent/BDM attribution — read LIVE so it works even on a fully cached
// page (the browser always sees the real URL + cookie). URL ?ataid=/?cid=
// first, then the 30-day agent_id/cid cookies set by atat-tracking.php.
var _qp = new URLSearchParams(window.location.search);
var _ataid = _qp.get(‘ataid’) || (document.cookie.match(/(?:^|;s*)agent_id=([^;]+)/) || [])[1] || ”;
var _cid = _qp.get(‘cid’) || (document.cookie.match(/(?:^|;s*)cid=([^;]+)/) || [])[1] || ”;
if (_ataid) data.agent_id = decodeURIComponent(_ataid);
if (_cid) data.bdm_id = decodeURIComponent(_cid);
// Which page the form was actually on. Read live for the same reason
// as the attribution above: on a cached page the server has no idea
// which URL it is being served as, but the browser always does.
data._sourceUrl = window.location.href;
// …and what sent them to it. Read live for the same reason: a cached
// page cannot know either, but the browser does. Blank when they
// typed the address, used a bookmark, or came from an email client —
// which is the honest answer, not a gap to be filled in server-side.
data._referrerUrl = document.referrer || ”;
return data;
}

function uploadFiles(data) {
var fileInputs = form.querySelectorAll(‘input[type=file][data-crm-file-field]’);
var uploads = [];
fileInputs.forEach(function(el) {
if (!el.files || !el.files[0]) return;
var fd = new FormData();
fd.append(‘file’, el.files[0]);
var fieldName = el.name;
uploads.push(
fetch(crmApiUrl + ‘/public/forms/’ + “08ceeaae-4809-444e-ba0f-88da5836c463″ + ‘/upload’, { method: ‘POST’, body: fd })
.then(function(r) {
if (!r.ok) throw new Error(‘File upload failed (‘ + r.status + ‘)’);
return r.json();
})
.then(function(res) {
if (res.path) data[fieldName] = res.path;
else throw new Error(res.error || ‘File upload failed’);
})
);
});
return Promise.all(uploads).then(function() { return data; });
}

function submitFormData(data) {
var body = new FormData();
body.append(‘action’, ‘p118_crm_submit’);
body.append(‘form_id’, “08ceeaae-4809-444e-ba0f-88da5836c463″);
body.append(‘data’, JSON.stringify(data));
// Embed-page context for GF-style merge tags ({embed_url},
// {embed_post:post_title}, {embed_post:ID}). Captured PHP-side
// at render time, then echoed to JS so the submit fetch can
// forward to V2 as request headers.
body.append(‘embed_url’, “”);
body.append(‘embed_post_id’, “0”);
body.append(‘embed_post_title’, “”);

return fetch(“https://www.property118.com/wp-admin/admin-ajax.php”, { method: ‘POST’, body: body, credentials: ‘same-origin’ })
.then(function(r){ return r.json(); })
.then(function(res){
var p = res.data || res;
if (p && p.success) {
if (p.confirmationType === ‘form’ && p.nextFormId) {
return swapInNextForm(p.nextFormId, p.prefill || {});
}
if (p.confirmationType === ‘redirect’ && p.confirmationRedirectUrl) {
window.location.href = p.confirmationRedirectUrl;
} else {
form.style.display = ‘none';
msg.className = ‘crm-message success';
msg.innerHTML = p.confirmationMessage || ‘Thank you for your submission.';

// Carry the calculator’s own results across. A calculator form
// renders its figures into [data-iht-results] as the visitor
// types; hiding the form hid those too, so the completion
// screen went blank and the visitor lost what they had just
// worked out. Nothing happens on forms without that element.
var resultsEl = form.querySelector(‘[data-iht-results]’);
if (resultsEl && resultsEl.innerHTML.trim()) {
var carried = document.createElement(‘div’);
carried.className = ‘crm-message-results';
carried.innerHTML = resultsEl.innerHTML;
msg.appendChild(carried);
}

// Download link for a generated report, present only when the
// API filed the PDF before responding. Built through the DOM
// rather than concatenated into innerHTML so the URL cannot
// inject markup.
if (p.reportDownloadUrl) {
var dlWrap = document.createElement(‘p’);
dlWrap.className = ‘crm-report-download';
var dl = document.createElement(‘a’);
dl.className = ‘crm-report-button';
// Building through the DOM stops markup injection but not a
// javascript: URL, which would run in this origin the moment
// the submitter clicks the button.
dl.href = /^https?:///i.test(String(p.reportDownloadUrl)) ? p.reportDownloadUrl : ‘#';
dl.target = ‘_blank';
dl.rel = ‘noopener noreferrer';
dl.textContent = ‘Download your report (PDF)';
dlWrap.appendChild(dl);
msg.appendChild(dlWrap);
}

msg.style.display = ‘block';
if (msg.scrollIntoView) {
msg.scrollIntoView({ behavior: ‘smooth’, block: ‘start’ });
}
}
} else {
throw new Error((p && p.error) || ‘Submission failed.’);
}
});
}

// Replace this whole form widget with another form, rendered server-side
// with the carried-over values seeded in. Inline injected via
// innerHTML won’t run, so we re-create each script node to execute it
// (this is what wires up the new form’s submit / conditional logic).
function swapInNextForm(nextFormId, prefill) {
var rbody = new FormData();
rbody.append(‘action’, ‘p118_crm_render_form’);
rbody.append(‘form_id’, nextFormId);
rbody.append(‘prefill’, JSON.stringify(prefill || {}));
return fetch(“https://www.property118.com/wp-admin/admin-ajax.php”, { method: ‘POST’, body: rbody, credentials: ‘same-origin’ })
.then(function(r){ return r.json(); })
.then(function(res2){
var pd = res2.data || res2;
if (!pd || !pd.html) { throw new Error((pd && pd.error) || ‘Could not load the next form.’); }
var frag = document.createElement(‘div’);
frag.innerHTML = pd.html;
var parent = wrap.parentNode;
var nodes = [];
while (frag.firstChild) {
var node = frag.firstChild;
parent.insertBefore(node, wrap);
nodes.push(node);
}
parent.removeChild(wrap);
function reexec(old) {
var s = document.createElement(‘script’);
for (var a = 0; a < old.attributes.length; a++) {
s.setAttribute(old.attributes[a].name, old.attributes[a].value);
}
if (!old.src) { s.textContent = old.textContent; }
old.parentNode.replaceChild(s, old);
}
nodes.forEach(function(n){
if (n.tagName === 'SCRIPT') { reexec(n); }
else if (n.querySelectorAll) {
var scripts = n.querySelectorAll('script');
for (var k = 0; k < scripts.length; k++) { reexec(scripts[k]); }
}
});
var first = nodes[0];
try { if (first && first.scrollIntoView) first.scrollIntoView({ behavior: 'smooth', block: 'start' }); } catch (e) {}
});
}

form.addEventListener('submit', function(e){
e.preventDefault();
// Validate required fields before submitting. Next only guards the pages
// before it, so the final page (and single-page forms) are checked here;
// jump to the first page that has a problem.
var vbad0 = crmFirstInvalid(null);
if (vbad0) { crmReportInvalid(vbad0); return; }
var data = collectFormData();
if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'Processing…'; }
msg.style.display = 'none';

// Standard form (no payment)
uploadFiles(data)
.then(function(d) { return submitFormData(d); })
.catch(function(err){
resetTurnstile();
if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = btnText; }
msg.className = 'crm-message error';
msg.textContent = err.message;
msg.style.display = 'block';
});
});

// ── Repeater fields (add / remove rows) ──────────────────────────
form.querySelectorAll( '.crm-repeater' ).forEach( function ( rep ) {
var rowsWrap = rep.querySelector( '.crm-repeater-rows' );
var tpl = rep.querySelector( '.crm-repeater-template' );
var addBtn = rep.querySelector( '.crm-repeater-add' );
if ( addBtn && tpl && rowsWrap ) {
addBtn.addEventListener( 'click', function () {
var idx = parseInt( rep.getAttribute( 'data-next-index' ) || '1', 10 );
var tmp = document.createElement( 'div' );
tmp.innerHTML = tpl.innerHTML.split( '__ROW__' ).join( idx );
var row = tmp.firstElementChild;
if ( ! row ) { return; }
row.setAttribute( 'data-row', idx );
rowsWrap.appendChild( row );
rep.setAttribute( 'data-next-index', idx + 1 );
rep.dispatchEvent( new CustomEvent( 'crm-repeater-change', { bubbles: true } ) );
} );
}
if ( rowsWrap ) {
rowsWrap.addEventListener( 'click', function ( e ) {
var btn = e.target.closest ? e.target.closest( '.crm-repeater-remove' ) : null;
if ( ! btn ) { return; }
if ( rowsWrap.querySelectorAll( '.crm-repeater-row' ).length <= 1 ) { return; }
var r = btn.closest( '.crm-repeater-row' );
if ( r ) { r.remove(); }
rep.dispatchEvent( new CustomEvent( 'crm-repeater-change', { bubbles: true } ) );
} );
}
} );
})();

The post PRS Database tipping point – Turning landlord problems into cash in the bank appeared first on Property118.

View Full Article: PRS Database tipping point – Turning landlord problems into cash in the bank

Sep
28

Landlords need an energy roadmap for 2030 targets

Author admin    Category Uncategorized     Tags

Property118

Landlords need an energy roadmap for 2030 targets

Landlords need the government to provide a clear roadmap to help prepare for the sector’s 2030 energy efficiency targets.

The warning comes from the National Residential Landlords Association (NRLA) and Energy Saving Trust.

They say that leaving investment until nearer the deadline could put installers, supply chains and council enforcement teams under pressure at the same time.

Their joint briefing calls for ministers to explain how Minimum Energy Efficiency Standards (MEES) will fit alongside changes to Energy Performance Certificates, exemptions and financial support.

With four years to prepare, the organisations say delivery depends on accessible finance, enough skilled tradespeople and reliable advice.

Landlord energy improvements

The NRLA’s chief executive, Ben Beadle, said: “Landlords want to provide homes that are warmer, cheaper to heat and fit for the future.

“Whilst the changes announced by the government earlier this year have made the proposed framework more workable, the focus now needs to be on making sure it can actually be delivered.”

He added: “2030 may sound some way off, but upgrading homes on the scale now needed requires landlords, installers, lenders, local authorities and tenants to start preparing now.

“That means providing urgent certainty about what will be required, ensuring the tradespeople and finance that landlords will need are available to carry out the work.”

Finance for improvements

The report on how to deliver the 2030 energy targets follows a parliamentary roundtable chaired by Clive Betts MP, the former Housing Select Committee chair, involving the two organisations and representatives from 10 others.

He said: “Improving the energy efficiency of the rental housing stock is a vital part of the government’s mission to address cost-of-living pressures and improve conditions across the sector.

“Ministers now need to turn welcome ambitions into reality on the ground.”

Participants welcomed the direction provided by the Warm Homes Plan and the revised approach to MEES for privately rented homes.

However, they called for the government to work with lenders and the wider sector to support early investment and ensure publicly funded improvements deliver fairer outcomes for tenants.

Advice and enforcement

Businesses also need sufficient certainty about future demand to invest in training and apprenticeships, alongside stronger checks on the quality of retrofit work.

The planned Warm Homes Agency should provide impartial advice tailored to landlords and tenants, working with existing services, the briefing says.

It also calls for simpler complaints and redress procedures, with councils being properly resourced to enforce standards.

Older and disabled tenants, along with households at risk of fuel poverty, should receive tailored support and clear communication.

The Energy Saving Trust’s Stew Horne said: “New energy efficiency standards for the private rented sector are an important step towards upgrading privately rented homes to make them warmer and more efficient for renters.

“To ensure successful delivery of these standards, it is essential that the right support is in place for both landlords and tenants.”

He added that access to green finance products, such as low-cost loans, will support landlords to invest in their properties and bring in-depth advice to help navigate the energy requirements.

The post Landlords need an energy roadmap for 2030 targets appeared first on Property118.

View Full Article: Landlords need an energy roadmap for 2030 targets

Sep
25

Who will protect landlords when the threats begin?

Author admin    Category Uncategorized     Tags

Property118

Who will protect landlords when the threats begin?

Just when you think you’ve heard it all as a landlord, there comes a flurry of stories that are both appalling and sobering.

I’m talking about the Property118 community this week discussing the threats they have received from aggrieved tenants.

Threats to burn down a landlord’s home tend to stay with you.

When the person making the threat knows where you live, providing someone with a home can suddenly feel like putting your own family at risk.

Beneath a Property118 reader’s question, landlords have described death threats, intimidation and unwanted visits to their homes.

One says threats to kill them and burn down both their properties were recorded by police.

Another describes threatening telephone calls and someone pursuing a grievance to their former family home.

The original correspondent alleges that a tenant sabotaged their car’s brakes.

These are readers’ accounts, rather than independently established findings, but they deserve something better than being dismissed as landlords having another moan.

They explain why the forthcoming PRS Database has prompted questions that ministers should answer now.

Tell us who can see what

The government’s published guidance requires individual landlords to provide their name, date of birth, residential address, telephone number and email address.

It also says: “We’ll publish a list of information that the public will be able to access at a later stage.”

‘Later’. That’s a reassuring word for someone who has already had an angry tenant on their doorstep.

Let’s be clear: this does not establish that landlords’ home addresses or dates of birth will be published.

Collecting information and displaying it publicly are different things.

But why leave landlords guessing?

What if, and it is a big if, names, home addresses, dates of birth and contact numbers become publicly accessible?

What are landlords supposed to do then?

Change their telephone number? Move house? Their date of birth is rather harder to replace.

Or will access be restricted to tenants who can demonstrate that they hold a tenancy agreement?

If so, which details will they see, and why?

A verified tenancy would establish a relationship with the landlord.

It would not establish that the person seeking access poses no threat.

The experiences described by readers should make that distinction painfully obvious.

Privacy needs more than promises

How will these arrangements comply with UK GDPR?

The question deserves a proper explanation.

That’s because data protection requires lawful, fair and transparent processing, information limited to what is necessary, and appropriate security.

A government database must meet those requirements too.

There may be a legitimate reason to collect information for identification or enforcement.

That does not automatically justify showing the same information to tenants or strangers.

Ministers should explain why each personal detail is needed, who can access it and what safeguards exist for landlords with documented histories of threats.

Even information withheld from public view needs protection against unauthorised access.

Landlords are entitled to ask about that without being accused of opposing accountability.

Tenants need reliable ways to contact landlords, serve notices and enforce their rights.

Those arrangements should also protect the safety of the person receiving them.

Tenants pay for another worry

The consequences extend beyond the registration form.

Some readers say they will sell when their current tenants leave.

Others describe already putting properties on the market after frightening experiences.

We cannot measure a national trend from a comment thread.

We can, however, recognise the decision being described: the income no longer compensates for the worry.

For landlords already weighing rising costs, greater obligations and the possibility of a difficult tenancy, uncertainty about personal security adds another reason to stop.

The next applicant then has one fewer home to consider.

Those who remain may face landlords increasingly unwilling to take a chance.

Where does this end?

Before demanding personal information, the government should explain precisely how it will protect it.

Would the Housing Minister be comfortable having their home address, date of birth and telephone number accessible through the same system? If the answer is no, ministers should understand why landlords are asking these questions.

Having something to protect is not the same as having something to hide.

Landlords should not have to wait until something goes wrong to discover that the safeguards they expected were never there.

Until next time,

The Landlord Crusader

The post Who will protect landlords when the threats begin? appeared first on Property118.

View Full Article: Who will protect landlords when the threats begin?

Sep
25

Tenants rate landlords positively despite public criticism

Author admin    Category Uncategorized     Tags

Property118

Tenants rate landlords positively despite public criticism

Private tenants describe their landlords positively despite the criticism they face in public debate, according to research from Yorkshire Building Society.

Just 5% of those renting privately described their landlord as exploitative.

The lender surveyed 4,000 adults for its policy paper, No Way Home? Restoring Britain’s Housing Ladder.

Among private tenants, 40% described their landlord as reasonable and the same proportion said theirs was approachable.

Tenants’ views of landlords

The building society’s managing director of homes, Tom Simpson, said: “Landlords are often portrayed negatively in public debate, yet our findings show a very different picture among those with direct experience of renting, demonstrating a clear disconnect between perception and reality.

“The vast majority of landlords provide homes that tenants rely on every day, and most are viewed positively by the people living in their properties.”

He added: “The conversation around housing should be balanced and evidence-based.

“Our research suggests that while perceptions of landlords can often be shaped by wider narratives, the lived experience of many renters is much more positive.”

Private landlords offer choice

Asked to describe landlords in their own words, respondents most used terms including ‘good’, ‘professional’, ‘friendly’ and ‘understanding’.

Among private tenants, 36% described their landlord as fair and 29% as responsive.

Negative assessments included being negligent, selected by 9%, unresponsive by 8% and unreasonable by 7%.

Across those surveyed, 60% agreed that private landlords are vital because they offer choice.

Support for responsible landlords

Meanwhile, 72% of respondents agreed that renting provides flexibility for people who do not wish to buy a home.

The society, which owns Accord Mortgages, also calls for more support for responsible landlords to encourage them to maintain and expand the supply of good-quality homes to rent.

The findings were released to mark the 30th anniversary of the first buy to let mortgage.

Around one in five households in England live in privately rented accommodation.

The post Tenants rate landlords positively despite public criticism appeared first on Property118.

View Full Article: Tenants rate landlords positively despite public criticism

Sep
24

Landlords: Are you waiting for Easter?

Author admin    Category Uncategorized     Tags

Property118

Landlords: Are you waiting for Easter?

We regularly speak to landlords who knew they wanted to leave the PRS or downsize after years of rising costs, increasing regulation, tenant problems, maintenance demands and diminishing returns, years before they finally spoke to us.

They wanted out but could never find the right time to leave. We know that’s still true and there are thousands more landlords putting their plans on hold – either waiting for things to improve or get so bad, the decision is made for them.

Some landlords who have held on might be thinking the biggest Renters’ Rights Act changes are already behind them. Unfortunately, they may need to think again.

The next phase begins on 15 December, when the PRS database starts rolling out across England – and 2027 will bring even more changes, including new property income tax rates of 22%, 42% and 47% and Making Tax Digital.

For landlords who have already decided they do not want to be part of the increasingly regulated PRS, that date creates a clear incentive to sell sooner rather than later.

The problem is that time is already running short.

Rightmove says it now takes an average of 216 days to find a buyer and complete a property sale. That means the landlords listing vacant property with a high street agent to sell to owner occupiers today could still be waiting at Easter.

A lot more can change between now and Easter.

With the Autumn budget on October 28th this year and 3 interest rate decisions between now and then it’s no wonder so many landlords are prioritising speed, certainty and convenience. They’re making sure they are protected from future challenges by selling to investors or property-buying companies who can complete faster and will buy with tenants in situ.

Both investors and property-buying companies value properties according to their suitability and potential yield. Where the buyer pool is small and limited to investors, open market value will often give way to yield. However, when a larger pool includes owner-occupiers, increased competition can force yield to give way to open market value.

Landlord Sales Agency offers the perfect solution: a good sales price determined by competition as well as yield, combined with a significantly faster and easier sale – without automatically having to evict your tenants first.

There is still time, but landlords who want to secure the best possible price and complete before the PRS database reaches their area need to act now.

Comprised of a team of the UK property industry’s leaders, we’ve developed a reputation for being the number one solution to go to for landlords looking to sell. We’re able to take entire portfolios off your hands, or sell individually, no matter what the tenant situation is, or the condition of the properties.

Connecting your buy-to-lets with our database of over 30,000 private buyers, a network of nationwide estate agents, local agents, and – if you need even more offers – our own private buying fund.

Our process generates a bidding war to drive up prices to ensure you’re getting the price you want. Most of our properties sell within 21 days.

We’ll also manage the entire sale for you. We know that right now landlords are more stressed than ever before. That’s no problem for us. All you have to do is get in touch, and we’ll manage the entire process for you, including negotiating with tenants, creating video tours of your properties and even funding refurbs to get you higher prices, depending on the property.

On top of that, we have a team of builders who can jump in and help and we’re happy to take on the costs – no problem, we’ll simply make those costs back on the sale.

While landlords still need to be realistic on price – it’s a tough market out there – we’re extremely confident that we can get you what you need to exit the market before the bills start mounting.

When a landlord with a portfolio of 5 tenanted properties that spanned from Staffordshire all the way up to Newcastle Upon Tyne approached us, he wanted to sell up his portfolio fast to get out, but for the best possible price. We promised him exactly that. With a mixture of marketing to our entire network, including investors and first-time buyers, we were able to drive up the prices to ensure he was happy with the sale.

The first property sold in 2 days. 10 days after that 2 more sold. By day 24 the fourth property had sold in Durham, and by day 26 the last and final small property was sold in Lancashire.

The fifth property was sold to a buyer who was so happy with the photos and the video tour we’d got from the tenant, he made an offer without even viewing the property.

The only task the landlord had done throughout the sale was make the decision to contact us to sell. We handled everything else.

It really was that simple. £500,000 made in 26 days with zero hassle, worry or stress.

The landlord was able to sit back and enjoy just over 3 weeks of relaxation knowing he had the best team in the UK solving every single challenge for him.

So get in touch today: we’ve got the team to help you get cash in the bank before Easter. It’s time to cut your losses, and start re-investing elsewhere.

(function(){var el=document.getElementById(“ts-607776b5-3f7b-431c-b5cc-fc304603fee3″);if(!el)return;var b=document.body,h=document.documentElement;var dark=b.classList.contains(“dark-mode”)||b.classList.contains(“dark”)||b.classList.contains(“night-mode”)||h.classList.contains(“dark”);if(!dark){var bg=window.getComputedStyle(b).backgroundColor,m=bg.match(/d+/g);if(m)dark=(m[0]*0.299+m[1]*0.587+m[2]*0.114)<128;}el.setAttribute("data-theme",dark?"dark":"light");if(window.turnstile&&el.childElementCount===0){try{window.turnstile.render(el,{sitekey:el.getAttribute("data-sitekey"),theme:el.getAttribute("data-theme")});}catch(e){}}})();

(function(){
var uid = “crm-form-ba09a29b”;
var form = document.getElementById(uid + ‘-form’);
var wrap = document.getElementById(uid);
var msg = wrap.querySelector(‘.crm-message’);
var totalPages = 1;
var curPage = 0;

// ── Name casing ──────────────────────────────────────────────────────
// Tidy a name box as the visitor leaves it, so they are not staring at
// “JOHN SMITH” in their own form. Deliberately cosmetic: the API runs
// the same normalisation on submit and is the authority on what gets
// stored, so a disagreement here is harmless. Kept in step with
// packages/shared/src/names.ts.
var NAME_PARTICLES = ‘ van von de del della der den di da dal du la le el bin ibn al dos das do ter ten ‘;
var NAME_MACS = ‘ macdonald macintyre macleod macarthur macgregor macneil macpherson macrae mackinnon macqueen ‘;
function nameUp(s){ return s.replace(/[a-z]/i, function(c){ return c.toUpperCase(); }); }
function isParticle(w){ return NAME_PARTICLES.indexOf(‘ ‘ + w.toLowerCase() + ‘ ‘) >= 0; }
function nameRecase(atom){
if (!/[a-z]/i.test(atom)) return atom;
var dot = atom.charAt(atom.length – 1) === ‘.’ ? ‘.’ : ”;
var core = dot ? atom.slice(0, -1) : atom;
var lower = core.toLowerCase();
if (/^(?:II|III|IV|VI|VII|VIII|IX|XI|XII)$/.test(core.toUpperCase())) return core.toUpperCase() + dot;
if (lower === ‘jr’ || lower === ‘sr’) return nameUp(lower) + dot;
if (core.length === 1) return core.toUpperCase() + dot;
var mc = /^mc([a-z]{2,})$/.exec(lower);
if (mc) return ‘Mc’ + nameUp(mc[1]) + dot;
if (NAME_MACS.indexOf(‘ ‘ + lower + ‘ ‘) >= 0) return ‘Mac’ + nameUp(lower.slice(3)) + dot;
return nameUp(lower) + dot;
}
function nameWellFormed(atom){
// “Mccoll” has its leading capital but still wants McColl. Safe only
// because “Mc” is unambiguous — “Mac” deliberately has no such rule.
if (/^Mc[a-z]{2,}$/.test(atom)) return false;
return /^(?:[A-Z][a-z]*|[A-Z][a-z]+[A-Z][A-Za-z]*|O[A-Z][a-z]+|[A-Z]{1,3}|[IVX]+)$/.test(atom);
}
function tidyName(value){
var v = (value || ”).replace(/s+/g, ‘ ‘).replace(/^ | $/g, ”);
if (!v) return value;
if (/[@d,]|&[a-z]+;|&#|https?:|www./i.test(v)) return value; // not a name
var words = v.split(‘ ‘);
var deliberate = words.every(function(w){
// A lone particle is not a particle — it is someone’s name (Di, Al).
if (words.length > 1 && isParticle(w)) return true;
return w.split(/[-‘u2019.]/).filter(function(seg){ return /[a-z]/i.test(seg); })
.every(function(seg){ return nameWellFormed(seg); });
});
if (deliberate) return v;
return words.map(function(w, i){
if (i > 0 && isParticle(w)) return w.toLowerCase();
return w.split(/([-‘u2019.])/).map(function(seg){
return /^[-‘u2019.]$/.test(seg) ? seg : nameRecase(seg);
}).join(”);
}).join(‘ ‘);
}
// ── Thousand separators ──────────────────────────────────────────────
// Opt-in per field (data-thousands). Mirrors formatThousands() and the
// caret helpers in the CRM’s packages/shared/src/numbers.ts. The CRM
// strips the separators again on submit, so storage is unaffected.
function groupThousands(value){
var raw = String(value == null ? ” : value).replace(/,/g, ”);
if (!raw) return ”;
var neg = raw.charAt(0) === ‘-‘;
var body = neg ? raw.slice(1) : raw;
var dot = body.indexOf(‘.’);
var digits = function(x){ return x.replace(/[^0-9]/g, ”); };
var whole = digits(dot === -1 ? body : body.slice(0, dot));
var frac = dot === -1 ? null : digits(body.slice(dot + 1));
if (!whole && frac === null) return neg ? ‘-‘ : ”;
var grouped = whole.replace(/B(?=(d{3})+(?!d))/g, ‘,’);
return (neg ? ‘-‘ : ”) + grouped + (frac === null ? ” : ‘.’ + frac);
}
// Reposition by digit count, not character index: commas appear and
// vanish to the left of the caret, and counting characters would throw
// it to the end of the field on every keystroke.
function caretAfterGrouping(formatted, digitsBefore){
if (digitsBefore <= 0) return 0;
var seen = 0;
for (var i = 0; i parseFloat(cmp);
case ‘lessthan': return parseFloat(val) < parseFloat(cmp);
case 'contains': return val.indexOf(cmp) !== -1;
case 'startswith': return val.indexOf(cmp) === 0;
case 'endswith': return val.slice(-cmp.length) === cmp;
default: return false; // fail closed — mirror the CRM shared matcher
}
}

function applyConditionals() {
Object.keys(condMap).forEach(function(fieldId) {
var cond = condMap[fieldId];
var rules = cond.rules || [];
var match = cond.logicType === 'any'
? rules.some(evalRule)
: rules.every(evalRule);
var show = cond.actionType === 'show' ? match : !match;
var wrapper = form.querySelector('[data-field-id="' + fieldId + '"]');
if (!wrapper) {
var el = form.querySelector('[name="' + fieldId + '"], [name="' + fieldId + '[]"]');
if (el) wrapper = el.closest('.crm-field, .crm-half');
}
if (wrapper) wrapper.style.display = show ? '' : 'none';
});
}

form.addEventListener('change', applyConditionals);
form.addEventListener('input', applyConditionals);
applyConditionals();

// ── Required-field validation (Next + Submit) ────────────────────────
// Validate from the form's field config (id + type), NOT the [required]
// HTML attribute: a checkbox group can't carry a meaningful `required`
// (native means "tick every box"), so attribute checks skip it — which is
// why empty checkbox questions slipped past Next straight to submit.
var crmRequired = [{"id":"c364951b-ecb0-4a12-bfa4-9dbe564310e8","type":"name","label":"Name"},{"id":"910107df-b8a8-4541-879f-541cef9449e4","type":"email","label":"Email"},{"id":"3b317ed9-d327-4fe2-b92e-67efede67e6d","type":"phone","label":"Phone"},{"id":"c6fae2f0-c335-452b-aa03-2cc066163d71","type":"textarea","label":"Please give us details of how we can help and the properties in question"},{"id":"34c27d9f-b3d1-4a6d-b720-24000e9fa468","type":"checkbox","label":"Privacy Policy"}];
function crmWrapper(id) {
var w = form.querySelector('[data-field-id="' + id + '"]');
if (!w) { var el = form.querySelector('[name="' + id + '"], [name="' + id + '[]"]'); if (el) w = el.closest('.crm-field, .crm-half'); }
return w;
}
function crmFilled(fld) {
// The [name^="id["] arm catches composite fields. A name renders as id[first] /
// id[last] and an address as id[street] / id[city] / …, none of which match
// the two exact-name selectors – so els came back empty, the !els.length guard
// below returned true, and the field counted as filled. With novalidate on the
// form the browser's own required check is off too, so a required Name field
// could be left completely blank and both Next and Submit went through.
var els = form.querySelectorAll('[name="' + fld.id + '"], [name="' + fld.id + '[]"], [name^="' + fld.id + '["]');
if (!els.length) return true;
// Decide on what is actually RENDERED, not the declared type. A `product`
// field is choices (radio/checkbox) for a priced list but a single amount
// box for a user-defined price — and testing that box for .checked, which
// a text input never has, meant a required product field could never be
// satisfied. An invoice or "Request For Payment" form was unsubmittable:
// the payer typed an amount, hit Submit, and got "Please select at least
// one option." under the box, every time.
var checkable = Array.prototype.filter.call(els, function (el) {
return el.type === 'checkbox' || el.type === 'radio';
});
if (checkable.length) { return checkable.some(function (el) { return el.checked; }); }
if (['checkbox','radio','consent'].indexOf(fld.type) !== -1) { return false; }
// Test the sub-inputs the renderer actually marked required – a name field puts
// `required` on both first and last, an address field on none of its six – and
// fall back to the first input when nothing is marked, which is the existing
// single-input behaviour unchanged.
var reqEls = Array.prototype.filter.call(els, function (el) { return el.hasAttribute('required'); });
// Repeaters name their sub-inputs '[][]’, so the prefix arm above
// collects EVERY rendered row. Demanding all of them be filled means one blank
// extra row – added by mistake, or rendered by settings.minRows – blocks Next and
// Submit with a generic message on the wrapper. Only the first row is required.
var firstRow = form.querySelector(‘[data-field-id=”‘ + fld.id + ‘”] .crm-repeater-row’);
if (firstRow) {
reqEls = Array.prototype.filter.call(reqEls, function (el) { return firstRow.contains(el); });
}
var toTest = reqEls.length ? reqEls : [els[0]];
return Array.prototype.every.call(toTest, function (el) { return (el.value || ”).trim() !== ”; });
}
function crmFirstInvalid(pageIdx) {
for (var i = 0; i < crmRequired.length; i++) {
var fld = crmRequired[i]; var w = crmWrapper(fld.id);
if (!w) continue;
if (w.style.display === 'none') continue;
if (pageIdx != null) { var pd = w.closest('[data-page]'); if (!pd || parseInt(pd.getAttribute('data-page')) !== pageIdx) continue; }
if (!crmFilled(fld)) return fld;
}
return null;
}
// Shows the error in a red box UNDER the field, matching the form's own
// .crm-message error styling, and scrolls to it.
function crmMarkError(w, text) {
if (!w) return;
var e = w.querySelector('.crm-inline-error');
if (!e) { e = document.createElement('div'); e.className = 'crm-inline-error'; w.appendChild(e); }
e.style.cssText = 'background:#fee2e2;color:#991b1b;padding:.55rem .75rem;border-radius:4px;margin-top:.4rem;font-size:.9rem';
e.textContent = text || 'Please answer this before continuing.';
}
function crmClearError(w) { if (!w) return; var e = w.querySelector('.crm-inline-error'); if (e) e.parentNode.removeChild(e); }
function crmReportInvalid(fld) {
var w = crmWrapper(fld.id);
// Match the message to what the visitor is actually looking at — an amount
// box should not be told to "select an option".
var els = form.querySelectorAll('[name="' + fld.id + '"], [name="' + fld.id + '[]"]');
var hasChoices = Array.prototype.some.call(els, function (el) {
return el.type === 'checkbox' || el.type === 'radio';
});
var t;
if (fld.type === 'product' && !hasChoices) { t = 'Please enter an amount.'; }
else if (['checkbox','product'].indexOf(fld.type) !== -1) { t = 'Please select at least one option.'; }
else if (fld.type === 'radio') { t = 'Please choose an option.'; }
else { t = 'Please fill this in.'; }
crmMarkError(w, t);
if (w && w.scrollIntoView) w.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
form.addEventListener('change', function(e){ var w = e.target && e.target.closest ? e.target.closest('.crm-field, .crm-half, [data-field-id]') : null; if (w) crmClearError(w); });
form.addEventListener('input', function(e){ var w = e.target && e.target.closest ? e.target.closest('.crm-field, .crm-half, [data-field-id]') : null; if (w) crmClearError(w); });

// ── Multi-page navigation ────────────────────────────────────────────

// ── Submit ────────────────────────────────────────────────────────────
var submitBtn = form.querySelector('button[type=submit]');
var btnText = submitBtn ? submitBtn.textContent : 'Submit';

var crmApiUrl = "https://crm-api.property118.com";

function collectFormData() {
var data = {};
for (var j = 0; j < form.elements.length; j++) {
var el = form.elements[j];
if (!el.name) continue;
if (el.type === 'file') continue; // handled by uploadFiles()
if (el.type === 'radio' && !el.checked) continue;
if (el.type === 'checkbox') {
if (!el.checked) continue;
var k = el.name.replace('[]','');
data[k] = data[k] ? data[k].concat([el.value]) : [el.value];
} else {
data[el.name] = el.value;
}
}
// Repeater fields: sub-inputs are named "[][]”.
// Group those flat keys into an array of row objects under the repeater
// id. Plain fields, checkbox “[]” arrays and single-bracket composites
// (name[first], address[city]) don’t match the double-bracket pattern, so
// ordinary submissions are unchanged.
var _repRe = /^(.+?)[(d+)][(.+)]$/;
var _repIds = {};
Object.keys( data ).forEach( function ( key ) {
var m = key.match( _repRe );
if ( ! m ) { return; }
var rid = m[1], row = parseInt( m[2], 10 ), sub = m[3];
_repIds[ rid ] = true;
if ( ! Array.isArray( data[ rid ] ) ) { data[ rid ] = []; }
if ( ! data[ rid ][ row ] || typeof data[ rid ][ row ] !== ‘object’ ) { data[ rid ][ row ] = {}; }
data[ rid ][ row ][ sub ] = data[ key ];
delete data[ key ];
} );
Object.keys( _repIds ).forEach( function ( rid ) {
if ( Array.isArray( data[ rid ] ) ) {
data[ rid ] = data[ rid ].filter( function ( r ) { return r && typeof r === ‘object'; } );
}
} );
// GF auto-substituted {user_agent} / {referer} on hidden fields
// at render time. We do the equivalent right before submit so
// fields whose default value carries these placeholders
// resolve to the browser’s actual values rather than being
// stored as literal “{user_agent}” / “{referer}” strings.
// The last ARTICLE the visitor read (set client-side on post views by
// P118_Article_Views). Used to attribute the enquiry to the article
// even after navigating away or with a stripped/absent referer.
var lastPost = ”;
try {
var lpm = document.cookie.match(/(?:^|;s*)p118_last_post=([^;]+)/);
if (lpm) lastPost = decodeURIComponent(lpm[1]);
} catch (e) {}
var subs = {
‘{user_agent}': navigator.userAgent || ”,
// Prefer the last article read; fall back to the (lossy) HTTP referer.
‘{referer}': lastPost || document.referrer || ”,
‘{last_post}': lastPost || ”,
‘{embed_url}': window.location.href || ”,
};
for (var name in data) {
if (!data.hasOwnProperty(name)) continue;
var v = data[name];
if (typeof v !== ‘string’) continue;
for (var tag in subs) {
if (v.indexOf(tag) !== -1) v = v.split(tag).join(subs[tag]);
}
data[name] = v;
}
// Agent/BDM attribution — read LIVE so it works even on a fully cached
// page (the browser always sees the real URL + cookie). URL ?ataid=/?cid=
// first, then the 30-day agent_id/cid cookies set by atat-tracking.php.
var _qp = new URLSearchParams(window.location.search);
var _ataid = _qp.get(‘ataid’) || (document.cookie.match(/(?:^|;s*)agent_id=([^;]+)/) || [])[1] || ”;
var _cid = _qp.get(‘cid’) || (document.cookie.match(/(?:^|;s*)cid=([^;]+)/) || [])[1] || ”;
if (_ataid) data.agent_id = decodeURIComponent(_ataid);
if (_cid) data.bdm_id = decodeURIComponent(_cid);
// Which page the form was actually on. Read live for the same reason
// as the attribution above: on a cached page the server has no idea
// which URL it is being served as, but the browser always does.
data._sourceUrl = window.location.href;
// …and what sent them to it. Read live for the same reason: a cached
// page cannot know either, but the browser does. Blank when they
// typed the address, used a bookmark, or came from an email client —
// which is the honest answer, not a gap to be filled in server-side.
data._referrerUrl = document.referrer || ”;
return data;
}

function uploadFiles(data) {
var fileInputs = form.querySelectorAll(‘input[type=file][data-crm-file-field]’);
var uploads = [];
fileInputs.forEach(function(el) {
if (!el.files || !el.files[0]) return;
var fd = new FormData();
fd.append(‘file’, el.files[0]);
var fieldName = el.name;
uploads.push(
fetch(crmApiUrl + ‘/public/forms/’ + “08ceeaae-4809-444e-ba0f-88da5836c463″ + ‘/upload’, { method: ‘POST’, body: fd })
.then(function(r) {
if (!r.ok) throw new Error(‘File upload failed (‘ + r.status + ‘)’);
return r.json();
})
.then(function(res) {
if (res.path) data[fieldName] = res.path;
else throw new Error(res.error || ‘File upload failed’);
})
);
});
return Promise.all(uploads).then(function() { return data; });
}

function submitFormData(data) {
var body = new FormData();
body.append(‘action’, ‘p118_crm_submit’);
body.append(‘form_id’, “08ceeaae-4809-444e-ba0f-88da5836c463″);
body.append(‘data’, JSON.stringify(data));
// Embed-page context for GF-style merge tags ({embed_url},
// {embed_post:post_title}, {embed_post:ID}). Captured PHP-side
// at render time, then echoed to JS so the submit fetch can
// forward to V2 as request headers.
body.append(‘embed_url’, “”);
body.append(‘embed_post_id’, “0”);
body.append(‘embed_post_title’, “”);

return fetch(“https://www.property118.com/wp-admin/admin-ajax.php”, { method: ‘POST’, body: body, credentials: ‘same-origin’ })
.then(function(r){ return r.json(); })
.then(function(res){
var p = res.data || res;
if (p && p.success) {
if (p.confirmationType === ‘form’ && p.nextFormId) {
return swapInNextForm(p.nextFormId, p.prefill || {});
}
if (p.confirmationType === ‘redirect’ && p.confirmationRedirectUrl) {
window.location.href = p.confirmationRedirectUrl;
} else {
form.style.display = ‘none';
msg.className = ‘crm-message success';
msg.innerHTML = p.confirmationMessage || ‘Thank you for your submission.';

// Carry the calculator’s own results across. A calculator form
// renders its figures into [data-iht-results] as the visitor
// types; hiding the form hid those too, so the completion
// screen went blank and the visitor lost what they had just
// worked out. Nothing happens on forms without that element.
var resultsEl = form.querySelector(‘[data-iht-results]’);
if (resultsEl && resultsEl.innerHTML.trim()) {
var carried = document.createElement(‘div’);
carried.className = ‘crm-message-results';
carried.innerHTML = resultsEl.innerHTML;
msg.appendChild(carried);
}

// Download link for a generated report, present only when the
// API filed the PDF before responding. Built through the DOM
// rather than concatenated into innerHTML so the URL cannot
// inject markup.
if (p.reportDownloadUrl) {
var dlWrap = document.createElement(‘p’);
dlWrap.className = ‘crm-report-download';
var dl = document.createElement(‘a’);
dl.className = ‘crm-report-button';
// Building through the DOM stops markup injection but not a
// javascript: URL, which would run in this origin the moment
// the submitter clicks the button.
dl.href = /^https?:///i.test(String(p.reportDownloadUrl)) ? p.reportDownloadUrl : ‘#';
dl.target = ‘_blank';
dl.rel = ‘noopener noreferrer';
dl.textContent = ‘Download your report (PDF)';
dlWrap.appendChild(dl);
msg.appendChild(dlWrap);
}

msg.style.display = ‘block';
if (msg.scrollIntoView) {
msg.scrollIntoView({ behavior: ‘smooth’, block: ‘start’ });
}
}
} else {
throw new Error((p && p.error) || ‘Submission failed.’);
}
});
}

// Replace this whole form widget with another form, rendered server-side
// with the carried-over values seeded in. Inline injected via
// innerHTML won’t run, so we re-create each script node to execute it
// (this is what wires up the new form’s submit / conditional logic).
function swapInNextForm(nextFormId, prefill) {
var rbody = new FormData();
rbody.append(‘action’, ‘p118_crm_render_form’);
rbody.append(‘form_id’, nextFormId);
rbody.append(‘prefill’, JSON.stringify(prefill || {}));
return fetch(“https://www.property118.com/wp-admin/admin-ajax.php”, { method: ‘POST’, body: rbody, credentials: ‘same-origin’ })
.then(function(r){ return r.json(); })
.then(function(res2){
var pd = res2.data || res2;
if (!pd || !pd.html) { throw new Error((pd && pd.error) || ‘Could not load the next form.’); }
var frag = document.createElement(‘div’);
frag.innerHTML = pd.html;
var parent = wrap.parentNode;
var nodes = [];
while (frag.firstChild) {
var node = frag.firstChild;
parent.insertBefore(node, wrap);
nodes.push(node);
}
parent.removeChild(wrap);
function reexec(old) {
var s = document.createElement(‘script’);
for (var a = 0; a < old.attributes.length; a++) {
s.setAttribute(old.attributes[a].name, old.attributes[a].value);
}
if (!old.src) { s.textContent = old.textContent; }
old.parentNode.replaceChild(s, old);
}
nodes.forEach(function(n){
if (n.tagName === 'SCRIPT') { reexec(n); }
else if (n.querySelectorAll) {
var scripts = n.querySelectorAll('script');
for (var k = 0; k < scripts.length; k++) { reexec(scripts[k]); }
}
});
var first = nodes[0];
try { if (first && first.scrollIntoView) first.scrollIntoView({ behavior: 'smooth', block: 'start' }); } catch (e) {}
});
}

form.addEventListener('submit', function(e){
e.preventDefault();
// Validate required fields before submitting. Next only guards the pages
// before it, so the final page (and single-page forms) are checked here;
// jump to the first page that has a problem.
var vbad0 = crmFirstInvalid(null);
if (vbad0) { crmReportInvalid(vbad0); return; }
var data = collectFormData();
if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'Processing…'; }
msg.style.display = 'none';

// Standard form (no payment)
uploadFiles(data)
.then(function(d) { return submitFormData(d); })
.catch(function(err){
resetTurnstile();
if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = btnText; }
msg.className = 'crm-message error';
msg.textContent = err.message;
msg.style.display = 'block';
});
});

// ── Repeater fields (add / remove rows) ──────────────────────────
form.querySelectorAll( '.crm-repeater' ).forEach( function ( rep ) {
var rowsWrap = rep.querySelector( '.crm-repeater-rows' );
var tpl = rep.querySelector( '.crm-repeater-template' );
var addBtn = rep.querySelector( '.crm-repeater-add' );
if ( addBtn && tpl && rowsWrap ) {
addBtn.addEventListener( 'click', function () {
var idx = parseInt( rep.getAttribute( 'data-next-index' ) || '1', 10 );
var tmp = document.createElement( 'div' );
tmp.innerHTML = tpl.innerHTML.split( '__ROW__' ).join( idx );
var row = tmp.firstElementChild;
if ( ! row ) { return; }
row.setAttribute( 'data-row', idx );
rowsWrap.appendChild( row );
rep.setAttribute( 'data-next-index', idx + 1 );
rep.dispatchEvent( new CustomEvent( 'crm-repeater-change', { bubbles: true } ) );
} );
}
if ( rowsWrap ) {
rowsWrap.addEventListener( 'click', function ( e ) {
var btn = e.target.closest ? e.target.closest( '.crm-repeater-remove' ) : null;
if ( ! btn ) { return; }
if ( rowsWrap.querySelectorAll( '.crm-repeater-row' ).length <= 1 ) { return; }
var r = btn.closest( '.crm-repeater-row' );
if ( r ) { r.remove(); }
rep.dispatchEvent( new CustomEvent( 'crm-repeater-change', { bubbles: true } ) );
} );
}
} );
})();

The post Landlords: Are you waiting for Easter? appeared first on Property118.

View Full Article: Landlords: Are you waiting for Easter?

Categories

Archives

Calendar

September 2026
M T W T F S S
« Aug   Oct »
 123456
78910111213
14151617181920
21222324252627
282930  

Recent Posts

Quick Search

RSS More from Letting Links

Facebook Fan Page