Sayuri Moleskin Made To Measure Curtains (2024)

Scalpay Truffle Made to Measure Curtains Back to Curtains ShopParos Duck Egg Made To Measure Curtains
curtain_types.forEach((type, index) => { let inText = type.replace(/_/g, ' '); inText = this.wordCapitalize(inText); // 07/04/21 - 3" prefix to pleats if (type.includes("Pleat")) { inText = '"3 ' + inText; } inText = (inText.includes('Pleat')) ? '3" ' + inText : inText; // let divOption = document.createElement("div"); divOption.classList.add("option"); let optionImg = document.createElement("img"); optionImg.setAttribute('src', "/apps/init-proxy/assets/images/curtain_type/" + type + ".jpg"); optionImg.setAttribute('alt', `${inText} Image Preview`); optionImg.setAttribute("data-radio-value", type); let label = document.createElement("label"); label.setAttribute("value", type); label.innerText = inText; let radio = document.createElement("input"); radio.type = "radio"; radio.name = "curtain_type"; radio.value = type; if (index === 0) { radio.setAttribute('checked', 'checked'); } radio.addEventListener("change", (e) => { this.changeTotalCost(e); // Eyelet colours if (e.target.value === "eyelet") { this.flexElementById("eyelet-colours"); } else { this.hideElementById("eyelet-colours"); } }); let optionPrePriceResult = document.createElement("span"); optionPrePriceResult.setAttribute("id", `${type}_pre_result`); optionPrePriceResult.innerText = "£"; optionPrePriceResult.classList.add('no-display', 'strikethrough'); let optionPriceResult = document.createElement("span"); optionPriceResult.setAttribute("id", `${type}_result`); optionPriceResult.innerText = "£"; let optionFooter = document.createElement("div"); optionFooter.classList.add("flex"); optionFooter.classList.add("flex-start"); optionFooter.classList.add("align-center"); optionFooter.classList.add("option-footer"); divOption.appendChild(optionImg); divOption.appendChild(label); divOption.appendChild(optionPrePriceResult); divOption.appendChild(optionFooter); optionFooter.appendChild(radio); optionFooter.appendChild(optionPriceResult); // Render to DOM let curtainOptions = document.querySelector('.curtainOptions'); curtainOptions.appendChild(divOption); }); } else { // Not curtains, remove this section document.querySelector('.curtainOptions').remove(); document.querySelector('.eyeletColours').remove(); document.querySelector('.blindsOptions').classList.add('no-display'); // Do not display document.querySelector('#blindsRadioErr').classList.add('no-display'); // Do not display document.querySelector('#eyeletColourErr').classList.add('no-display'); // Do not display document.querySelector('#curtainRadioErr').classList.add('no-display'); // Do not display document.querySelector('.selectedOption').classList.add('no-display'); // Do not display this.highest_max_width = 0; // Blinds has dynamic high limit this.highest_max_length = 240; Object.entries(blind_types).forEach((type, index) => { if (type[1].max_width > this.highest_max_width) { // Raise max_width highest limit to match highest blind_type max_width this.highest_max_width = type[1].max_width } }); let inputPoleTrackWidth = document.getElementById('poletrackwidth'); let labelPoleTrackWidth = document.getElementById('label-poletrackwidth'); inputPoleTrackWidth.setAttribute('placeholder', "30 - " + this.highest_max_width); inputPoleTrackWidth.setAttribute('max', this.highest_max_width); labelPoleTrackWidth.innerText = "Enter your width"; } // poletrackwidth let domHighestPoleTrackWidth = document.getElementById('highest_max_width'); domHighestPoleTrackWidth.innerText = this.highest_max_width; // drop/length_req let inputLengthReq = document.getElementById('length_req'); inputLengthReq.setAttribute('placeholder', "30 - " + this.highest_max_length); inputLengthReq.setAttribute('max', this.highest_max_length); let domHighestLengthReq = document.getElementById('highest_max_length'); domHighestLengthReq.innerText = this.highest_max_length; // Render Lining Options lining_types.forEach((type, index) => { // Label making console.log(type) let label = document.createElement("label"); label.setAttribute("value", type); let inText = type.replace(/_/g, ' '); inText = this.wordCapitalize(inText); label.innerText = inText; // Promo if(this._data.entity.promo_linings_blackout && type == 'blackout') { let promoSpan = document.createElement("span"); promoSpan.classList.add('lining_promo'); promoSpan.innerText = "Free"; promoSpan.style.backgroundImage = "url('/apps/init-proxy/assets/images/promo/Free-Blur2.svg')"; label.appendChild(promoSpan); } // Input making let radio = document.createElement("input"); radio.type = "radio"; radio.name = "lining_type"; radio.value = type; if (index === 0) { radio.setAttribute('checked', 'checked'); } radio.addEventListener("change", (e) => { this.hideElementById("mtmPt2container"); }); let optionFooter = document.createElement("div"); optionFooter.classList.add("flex"); optionFooter.classList.add("flex-start"); optionFooter.classList.add("align-center"); optionFooter.classList.add("option-footer"); // Render to DOM let liningOptions = document.querySelector('.liningOptions'); liningOptions.appendChild(optionFooter); optionFooter.appendChild(radio); optionFooter.appendChild(label); }); let domPoleTrackWidth = document.getElementById('poletrackwidth'); let domLengthReq = document.getElementById('length_req'); domPoleTrackWidth.addEventListener("keydown", (e) => { this.hideElementById("mtmPt2container") }); domPoleTrackWidth.addEventListener("input", (e) => { this.hideElementById("mtmPt2container") }); domPoleTrackWidth.addEventListener("change", (e) => { this.hideElementById("mtmPt2container") }); domLengthReq.addEventListener("keydown", (e) => { this.hideElementById("mtmPt2container") }); domLengthReq.addEventListener("input", (e) => { this.hideElementById("mtmPt2container") }); domLengthReq.addEventListener("change", (e) => { this.hideElementById("mtmPt2container") }); // Button triggers update const instantPriceBtn = document.getElementById('instant-price-btn'); // Passing in true so updateProduct knows this was triggered via this button instantPriceBtn.addEventListener('click', this.updateProduct.bind(this, true)); const generateBtn = document.getElementById('mtm-generate-btn'); generateBtn.addEventListener('click', (e) => { this.addToBasket(e) }); // Modal trigger let modalBtns = document.querySelectorAll("button[data-modal]"); modalBtns.forEach((button) => { button.addEventListener('click', (e) => { this.toggleModal(e) }); }); // Tab trigger let tabs = document.querySelectorAll("button[data-tab]"); tabs.forEach((tab) => { tab.addEventListener('click', (e) => { this.toggleTab(e) }); }); // Convert trigger let inputs = document.querySelectorAll("input[data-res]"); inputs.forEach((input) => { input.addEventListener('keyup', (e) => { this.convertInchesToCm(e) }); }); // Free Sample let domFabricBtn = document.getElementById("free-sample-btn"); let product_title = domFabricBtn.getAttribute('data-product-title'); let product_sku = domFabricBtn.getAttribute('data-fabric-sku'); if (this.sampleIsInCart()) { domFabricBtn.disabled = true; domFabricBtn.classList.add("disabled-btn"); domFabricBtn.textContent = "Free Sample in Cart!"; } else { if (this._data.entity.sample_ref) { domFabricBtn.addEventListener('click', (e) => { this.freeSampleToBasket(e, product_title) }); } else { domFabricBtn.addEventListener('click', (e) => { this.generateSample(e, product_sku, product_title) }); } } // Initial update this.updateProduct(); // HideMask this.hideElementById("mtm-loading-mask"); // Enable img click to relative radio inputs ---t let optionImages = document.querySelectorAll('.option img'); for (var i = 0, len = optionImages.length; i < len; i++) { let optionImage = optionImages[i]; optionImage.addEventListener('click', (e) => { if (!e.target.hasAttribute("data-radio-value")) { // data attribute doesn't exist return false; } let radioValue = e.target.getAttribute("data-radio-value"); let targetRadio = document.querySelector(`input[value='${radioValue}']`) targetRadio.checked = true; // Display eyelet options when eyelet option is selected if (radioValue === "eyelet" || e.target.classList.contains('img-eyelet')) { this.flexElementById("eyelet-colours"); } else { this.hideElementById("eyelet-colours"); } // Update cost if (targetRadio.getAttribute('name') !== 'eyelet_colours') { let radioE = { target: targetRadio } this.changeTotalCost(radioE); } }) } } /* * Check cart object */ sampleIsInCart() { let sampleVariantID = this._data.entity.sample_ref; if (!sampleVariantID || sampleVariantID == "") { console.warn("sampleIsInCart - No sample_ref found") return false; } let result = false; let itemPropsFreeSampleVariantIdArr = []; result = itemPropsFreeSampleVariantIdArr.includes(sampleVariantID); return result; } /* * Free Sample to basket */ freeSampleToBasket(e, product_title) { let domErrorMsg = document.getElementById("free-sample-err"); if (!domErrorMsg.classList.contains("no-display")) { domErrorMsg.classList.add("no-display"); } let domSampleButton = e.target; if (!domSampleButton.classList.contains("loading")) { domSampleButton.classList.add("loading"); } let formData = { 'items': [{ quantity: 1, id: this._data.entity.sample_ref, properties: { 'Product': product_title, } }] }; fetch('/cart/add.js', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }) .then(function (response) { return response.json(); }) .then(data => { let resData = data; if (resData.status !== 200 && resData.status) { if (domErrorMsg.classList.contains("no-display")) { domErrorMsg.classList.remove("no-display"); } if (domSampleButton.classList.contains("loading")) { domSampleButton.classList.remove("loading"); domSampleButton.disabled = false; } console.error(resData); } else { window.location.reload(); } }) .catch((error) => { console.error('Error:', error); if (domErrorMsg.classList.contains("no-display")) { domErrorMsg.classList.remove("no-display"); } if (domSampleButton.classList.contains("loading")) { domSampleButton.classList.remove("loading"); domSampleButton.disabled = false; } }); }; /* * Generate Sample to basket */ async generateSample(e, product_sku, product_title) { let domSampleButton = e.target; if (!domSampleButton.classList.contains("loading")) { domSampleButton.classList.add("loading"); domSampleButton.disabled = true; } let domErrorMsg = document.getElementById("free-sample-err"); if (!domErrorMsg.classList.contains("no-display")) { domErrorMsg.classList.add("no-display"); } let payload = { 'product_sku': product_sku, 'shop': this._data.shop, 'product_type': this._data.product_type, 'product_page': this._data.product_page, 'images': this._given_data.images, }; let options = { url: "/apps/init-proxy/api/sample", method: "POST", headers: { 'Content-Type': 'application/json;charset=utf-8' }, body: JSON.stringify(payload) }; let response = await fetch(options.url, { method: options.method, headers: options.headers, body: options.body }); const jsonData = await response.json(); this._data.entity.sample_ref = jsonData.sample_ref; this.freeSampleToBasket(e, product_title) }; /* * Prepare data to send to api call */ addToBasket(e) { console.log("HELLO") this.updateProduct(); let payload = { shop: this._data.shop, product_type: this._data.product_type, product_page: this._data.product_page, length_req: this._given_data.length_req, curtain_type: this._given_data.curtain_type, blind_types: this._given_data.blind_types, lining_type: this._given_data.lining_type, poletrackwidth: this._given_data.poletrackwidth, fitting: this._given_data.fitting, control_position: this._given_data.control_position, eyelet_colour: this._given_data.eyelet_colour, images: this._given_data.images, total_price: this._total_cost, quantity: this._given_data.quantity } let test = this.apiCallGenerate(payload, e) .then(data => this.handleGeneratedResponse(data, e)) .catch(err => this.handleGenerateProductError(err)) } /* * Toggle modals */ toggleModal(e) { const id = e.currentTarget.getAttribute('data-modal'); console.log(id) let modal = document.getElementById(id); if (modal.classList.contains('no-display')) { modal.classList.remove('no-display'); } else { modal.classList.add('no-display'); } } /* * Toggle tabs */ toggleTab(e) { const id = e.target.getAttribute('data-tab'); const isActive = e.target.classList.contains('tab-active'); if (isActive) { return false; } let calculateTab = document.querySelector('button[data-tab="mtm-calculate"]'); let convertTab = document.querySelector('button[data-tab="mtm-convert"]'); let calculateSection = document.getElementById("mtm-calculate"); let convertSection = document.getElementById("mtm-convert"); if (id === "mtm-calculate") { calculateSection.classList.remove('no-display'); convertSection.classList.add('no-display'); e.target.classList.add('tab-active'); convertTab.classList.remove('tab-active'); } else { calculateSection.classList.add('no-display'); convertSection.classList.remove('no-display'); e.target.classList.add('tab-active'); calculateTab.classList.remove('tab-active'); } } /* * Convert inches to cm */ convertInchesToCm(e) { const id = e.target.getAttribute('data-res'); let resultSpan = document.getElementById(id); if (FormValidator.validateIsFloat(e.target.value) && e.target.value !== 0.00) { let result = e.target.value * 2.54; document.getElementById(id).textContent = result; } else { document.getElementById(id).textContent = 0; } } /* * Handle the response after attempting a product generation that did not error */ handleGeneratedResponse(data, e) { let addToBasketBtn = e.target; if (addToBasketBtn.classList.contains("loading")) { addToBasketBtn.classList.remove("loading"); } if (data.variant_id) { window.location.href = data.variant_id; } else { alert('Sorry, something went wrong') // Temporary error handler addToBasketBtn.disabled = false; } } /* * Will perform the API request to api/generate - Responding with error/invalidations/success (link) */ async apiCallGenerate(payload, e) { let addToBasketBtn = e.target; if (!addToBasketBtn.classList.contains("loading")) { addToBasketBtn.classList.add("loading"); addToBasketBtn.disabled = true; } console.log("CREATING NEW PRODUCT") console.log(payload) let options = { url: "/apps/init-proxy/api/generate", method: "POST", headers: { 'Content-Type': 'application/json;charset=utf-8' }, body: JSON.stringify(payload) }; let response = await fetch(options.url, { method: options.method, headers: options.headers, body: options.body }); const jsonData = await response.json(); // validateApiGenerate may throw errors as designed // const validData = FormValidator.validateApiGenerate(jsonData); // May contain errors return jsonData; } /* * */ updatePartTwoDom() { let domWidth = document.getElementById('widthRef'); let domDrop = document.getElementById('dropRef'); let domLining = document.getElementById('liningRef'); domWidth.textContent = this._given_data.poletrackwidth + "cm"; domDrop.textContent = this._given_data.length_req + "cm"; let liningText = this._given_data.lining_type; liningText = liningText.replace(/_/g, ' '); liningText = this.wordCapitalize(liningText); domLining.textContent = liningText; } /* * Gather image src's from product to use for the generated product */ scrapeImages() { let images = document.querySelectorAll('.img_ptw_mtm'); let imageArr = []; images.forEach((image) => { const imgUrls = image.dataset.srcset.split(',') if (image.src && typeof image.src !== "undefined") { imageArr.push("https:" + imgUrls[imgUrls.length-1].split(' ')[1]); } }); if (imageArr.length < 1) { return false; } return imageArr; } /* * */ gatherGivenData() { // Gather given data let poletrackwidth = document.getElementById('poletrackwidth').value; let length_req = document.getElementById('length_req').value; let curtain_type = this.getCheckedRadioValue("curtain_type"); let lining_type = this.getCheckedRadioValue('lining_type'); let fitting = this.getCheckedRadioValue('fitting'); let control_position = this.getCheckedRadioValue('control_position'); let quantity = (document.querySelector(".quantity input[name='quantity']")) ? document.querySelector(".quantity input[name='quantity']").value : 1; let eyelet_colour = this.getCheckedRadioValue('eyelet_colours'); let images = (this.scrapeImages()) ? JSON.stringify(this.scrapeImages()) : false; poletrackwidth = (poletrackwidth == "") ? 30 : poletrackwidth; length_req = (length_req == "") ? 30 : length_req; let given_data = { poletrackwidth, length_req, curtain_type, lining_type, fitting, control_position, quantity, eyelet_colour, images } this._given_data = given_data; } /* * Capitalize each word */ wordCapitalize(str) { var splitStr = str.toLowerCase().split(' '); for (var i = 0; i < splitStr.length; i++) { // You do not need to check if i is larger than splitStr length, as your for does that for you // Assign it back to the array splitStr[i] = splitStr[i].charAt(0).toUpperCase() + splitStr[i].substring(1); } // Directly return the joined string return splitStr.join(' '); } /* * Returns the checked value of radio selections */ getCheckedRadioValue(name) { var elements = document.getElementsByName(name); for (var i = 0, len = elements.length; i < len; ++i) if (elements[i].checked) return elements[i].value; } /* * Sets the data attribute 'data-price' to the element with the matching value * @param {String} id - Value of the dom element * @param {Integer/Float} price - The price to be given */ setDataPrice(value, price) { var element = document.querySelector(`input[value=${value}]`); element.setAttribute('data-price', price); } /* * Sets the data attribute 'data-sanspromoprice' to the element with the matching value * @param {String} id - Value of the dom element * @param {Integer/Float} price - The price to be given */ setDataSansPromoPrice(value, price) { var element = document.querySelector(`input[value=${value}]`); element.setAttribute('data-sanspromoprice', price); } /* * Sets the data attribute 'data-sanssaleprice' to the element with the matching value * @param {String} id - Value of the dom element * @param {Integer/Float} price - The price to be given */ setDataSansSalePrice(value, price) { var element = document.querySelector(`input[value=${value}]`); element.setAttribute('data-sanssaleprice', price); } /* * User has made changes and wants the price updated */ handleChange() { const poletrackwidth = document.getElementById('poletrackwidth').value; const length_req = document.getElementById('length_req').value; const liningTypes = document.querySelectorAll(".liningOptions > input[name='lining_type']"); const curtainTypes = document.querySelectorAll(".curtainOptions > input[name='curtain_type']"); let lining_type; let curtain_type; liningTypes.forEach((radio) => { if (radio.checked) { lining_type = radio.value; } }); curtainTypes.forEach((radio) => { if (radio.checked) { curtain_type = radio.value; } }); } /* CALCULATIONS */ /** * Run cosmetic calculations and render to page */ runCalculation(fetched_data, given_data) { // Temporary object based from given_data to calc potential prices let potentialGivenData = Object.assign({}, given_data); if (fetched_data.product_type === "curtains") { // Render potential prices for curtain types for (let [key, value] of Object.entries(fetched_data.curtain_types)) { potentialGivenData.curtain_type = value; let potentialTotalCosts = this.calculation(fetched_data, potentialGivenData); document.getElementById(`${value}_result`).textContent = `£${Number(potentialTotalCosts[1]).toFixed(2)}`; this.setDataPrice(value, Number(potentialTotalCosts[1]).toFixed(2)); this.setDataSansPromoPrice(value, Number(potentialTotalCosts[2]).toFixed(2)); this.setDataSansSalePrice(value, Number(potentialTotalCosts[0]).toFixed(2)); // Add pre-promo price to option - Also include sale if one is applied const domPreOptionPrice = document.getElementById(`${value}_pre_result`); domPreOptionPrice.textContent = ''; if(!domPreOptionPrice.classList.contains('no-display')) { domPreOptionPrice.classList.add('no-display'); } if(this._data.entity.promo_linings_blackout && given_data.lining_type == 'blackout') { const preOptionPrice = (Number(potentialTotalCosts[0]).toFixed(2) <= Number(potentialTotalCosts[2]).toFixed(2)) ? (this._data.entity.sale_percent) ? Number(potentialTotalCosts[0]).toFixed(2) : Number(potentialTotalCosts[2]).toFixed(2) : Number(potentialTotalCosts[2]).toFixed(2); domPreOptionPrice.textContent = `£${preOptionPrice}`; if(domPreOptionPrice.classList.contains('no-display')) { domPreOptionPrice.classList.remove('no-display'); } } } } const totalCosts = this.calculation(fetched_data, given_data); this.updateTotalCost(totalCosts); // while (domPrice.firstChild) { // domPrice.removeChild(domPrice.firstChild); // } // domPrice.appendChild(document.createTextNode("£" + totalCost)); // document.body.scrollIntoView({ // behavior: 'smooth', // Defines the transition animation. default: auto // }); } /* * Updates the class property _total_cost and render total to page. */ updateTotalCost(values) { this._total_cost = Number(values[1]).toFixed(2); let domPriceTop = document.getElementById('results-top'); let domPriceBottom = document.getElementById('results-bottom'); domPriceTop.textContent = `£${this._total_cost}`; domPriceBottom.textContent = `£${this._total_cost}`; if(values[0] !== values[1]) { // Final price differs and means a sale is in place let domSansSalePriceTop = document.getElementById('results-top-original'); let domSansSalePriceBottom = document.getElementById('results-bottom-original'); let domSalePercentTop = document.getElementById('mtm-sale-percent-top'); let domSalePercentBottom = document.getElementById('mtm-sale-percent-bottom'); domSansSalePriceTop.textContent = `£${values[0]}`; domSansSalePriceBottom.textContent = `£${values[0]}`; domSalePercentTop.textContent = `${this._data.entity.sale_percent}% off`; domSalePercentBottom.textContent = `${this._data.entity.sale_percent}% off`; } } /* * Updates the class property _total_cost and render total to page from a change event */ changeTotalCost(e) { const radio = e.target; // refers to the object that the handler is bound to. const radioPrice = radio.dataset.price; const radioSansSalePrice = radio.dataset.sanssaleprice; this._total_cost = Number(radioPrice).toFixed(2); let domPriceTop = document.getElementById('results-top'); let domPriceBottom = document.getElementById('results-bottom'); domPriceTop.textContent = `£${this._total_cost}`; domPriceBottom.textContent = `£${this._total_cost}`; let domSansSalePriceTop = document.getElementById('results-top-original'); let domSansSalePriceBottom = document.getElementById('results-bottom-original'); domSansSalePriceTop.textContent = ``; domSansSalePriceBottom.textContent = ``; if(this._data.entity.sale_percent) { domSansSalePriceTop.textContent = `£${Number(radioSansSalePrice).toFixed(2)}`; domSansSalePriceBottom.textContent = `£${Number(radioSansSalePrice).toFixed(2)}`; } } /** * * @param {Object} fetched_data - Contains required data fetched from database * @param {Object} given_data - Contains required data given from the user */ calculation(fetched_data, given_data) { // Given data const length_req = parseFloat(given_data.length_req); const quantity = 1; //parseFloat(given_data.quantity); const product_type = fetched_data.product_type; const type = (product_type === "blinds") ? null : given_data.curtain_type; const lining_type = given_data.lining_type; const poletrackwidth = parseFloat(given_data.poletrackwidth); // Fetched data - curtain type data let gather; let hem_usage; let makeup_cost_per_width; if (product_type === "curtains") { gather = parseFloat(fetched_data.supplier.curtain_type[type].gather); hem_usage = parseFloat(fetched_data.supplier.curtain_type[type].hem_usage); makeup_cost_per_width = parseFloat(fetched_data.supplier.curtain_type[type].makeup_cost_per_width); } else { hem_usage = parseFloat(fetched_data.supplier.blind_hem_usage); } // else { // gather = parseFloat(fetched_data.supplier.blinds_type[type].gather); // makeup_cost_per_width = parseFloat(fetched_data.supplier.blinds_type[type].makeup_cost_per_width); // } // Fetched data - entity type data const pattern_repeat = parseFloat(fetched_data.entity.fabric_pattern_repeat); const fabric_width = parseFloat(fetched_data.entity.fabric_width); const cut_price = parseFloat(fetched_data.entity.fabric_cut_price); const blind_types = fetched_data.supplier.blind_types; const blind_type_preference = fetched_data.entity.blind_type_preference; const markup = parseFloat(fetched_data.entity.markup); const sale_percent = fetched_data.entity.sale_percent; const promo_linings_blackout = fetched_data.entity.promo_linings_blackout; // Fetched data - lining type data const cost_of_lining = parseFloat(fetched_data.supplier.lining_type[lining_type].cost_of_lining); const promo_linings_blackout_cost_of_lining = (promo_linings_blackout && lining_type == 'blackout') ? parseFloat(fetched_data.supplier.lining_type['standard'].cost_of_lining) : parseFloat(fetched_data.supplier.lining_type[lining_type].cost_of_lining); //console.log('CALC', lining_type, cost_of_lining, promo_linings_blackout_cost_of_lining, (promo_linings_blackout && lining_type == 'blackout')) if (product_type === "blinds") { const blind_makeup_costs = fetched_data.supplier.blind_makeup_costs; const x = 8; const b_width_usage = (poletrackwidth > (fabric_width - x)) ? 2 : 1; const b_column1 = hem_usage + length_req; const b_widthMinusXGtTrackWidth = (poletrackwidth > (fabric_width - x)) ? Math.ceil((b_column1 / pattern_repeat)) * pattern_repeat : 0; const type_of_blind = this.calc_TYPE_OF_BLIND(blind_types, blind_type_preference, poletrackwidth); if (!type_of_blind) { alert('Placeholder error alert - type_of_blind is false'); return false; } // Calculated BLINDS data const BLINDS_LINING_REQ = this.calc_BLINDS_LINING_REQ(hem_usage, length_req, b_width_usage); const BLINDS_LINING_COST = this.calc_BLINDS_LINING_COST(BLINDS_LINING_REQ, cost_of_lining); const BLINDS_FINAL_FABRIC_USAGE = this.calc_BLINDS_FINAL_FABRIC_USAGE(b_column1, b_widthMinusXGtTrackWidth); const BLINDS_FABRIC_COST = this.calc_BLINDS_FABRIC_COST(BLINDS_FINAL_FABRIC_USAGE, cut_price); const BLINDS_COMPONENT_COST = type_of_blind.cost; const BLINDS_MAKEUP_COST = this.calc_BLINDS_MAKEUP_COST(poletrackwidth, blind_makeup_costs); const BLINDS_TOTAL_COST = this.calc_BLINDS_TOTAL_COST(BLINDS_LINING_COST, BLINDS_FABRIC_COST, BLINDS_COMPONENT_COST, BLINDS_MAKEUP_COST, markup); const BLINDS_FINAL_PRICE = (sale_percent && sale_percent > 0) ? this.calc_TOTAL_COST_AFTER_SALE_OPTION(sale_percent, BLINDS_TOTAL_COST) : BLINDS_TOTAL_COST; //console.log('BLINDS_TOTAL_COST', BLINDS_TOTAL_COST); //console.log('BLINDS_FINAL_PRICE', BLINDS_FINAL_PRICE); return [BLINDS_TOTAL_COST, BLINDS_FINAL_PRICE]; // let debugTable = [ // ["blind_makeup_costs", blind_makeup_costs], // ["b_width_usage", b_width_usage], // ["b_column1", b_column1], // ["b_widthMinusXGtTrackWidth", b_widthMinusXGtTrackWidth], // ["type_of_blind", type_of_blind], // ["BLINDS_LINING_REQ", BLINDS_LINING_REQ], // ["BLINDS_LINING_COST", BLINDS_LINING_COST], // ["BLINDS_FINAL_FABRIC_USAGE", BLINDS_FINAL_FABRIC_USAGE], // ["BLINDS_FABRIC_COST", BLINDS_FABRIC_COST], // ["BLINDS_COMPONENT_COST", BLINDS_COMPONENT_COST], // ["BLINDS_MAKEUP_COST", BLINDS_MAKEUP_COST], // ["BLINDS_TOTAL_COST", BLINDS_TOTAL_COST], // ]; //console.table(debugTable) } else { // Calculated data const HEM_LENGTHREQ = this.calc_HEM_LENGTHREQ(hem_usage, length_req); const FABRIC_USAGE = this.calc_FABRIC_USAGE(HEM_LENGTHREQ, pattern_repeat); const WIDTHS_NEEDED = this.calc_WIDTHS_NEEDED(gather, poletrackwidth, fabric_width); const TOTAL_FABRIC_REQ = this.calc_TOTAL_FABRIC_REQ(FABRIC_USAGE, WIDTHS_NEEDED); const LINING_REQ = this.calc_LINING_REQ(length_req, hem_usage, WIDTHS_NEEDED); const FABRIC_LINING_COSTS = this.calc_FABRIC_LINING_COSTS(TOTAL_FABRIC_REQ, cut_price, LINING_REQ, cost_of_lining); const PROMO_FABRIC_LINING_COSTS = this.calc_FABRIC_LINING_COSTS(TOTAL_FABRIC_REQ, cut_price, LINING_REQ, promo_linings_blackout_cost_of_lining); //console.log('CALC2', FABRIC_LINING_COSTS, PROMO_FABRIC_LINING_COSTS) const BEFORE_PROMO_TOTAL_COST = this.calc_TOTAL_COST(makeup_cost_per_width, WIDTHS_NEEDED, FABRIC_LINING_COSTS, markup); const AFTER_PROMO_TOTAL_COST = this.calc_TOTAL_COST(makeup_cost_per_width, WIDTHS_NEEDED, PROMO_FABRIC_LINING_COSTS, markup); const TOTAL_COST = (BEFORE_PROMO_TOTAL_COST !== AFTER_PROMO_TOTAL_COST) ? AFTER_PROMO_TOTAL_COST : BEFORE_PROMO_TOTAL_COST; const FINAL_PRICE = (sale_percent && sale_percent > 0) ? this.calc_TOTAL_COST_AFTER_SALE_OPTION(sale_percent, TOTAL_COST) : TOTAL_COST; //console.log('TOTAL_COST', TOTAL_COST) //console.log('FINAL_PRICE', FINAL_PRICE); return [TOTAL_COST, FINAL_PRICE, BEFORE_PROMO_TOTAL_COST]; } } /** * Calculates which blind_type to be used and returns the blind_type object * @param {Object} blind_types * @param {String} blind_type_preference * @param {Integer} poletrackwidth */ calc_TYPE_OF_BLIND(blind_types, blind_type_preference, poletrackwidth) { if (Object.keys(blind_types).length === 0) { // is empty object return false; } let width_limit = 0; for (const key in blind_types) { if (poletrackwidth <= blind_types[key].max_width) { width_limit = blind_types[key].max_width; break; } } let type_of_blind = Object.fromEntries( Object.entries(blind_types).filter(([key, value]) => key == blind_type_preference + "_" + width_limit) ) if (Object.keys(type_of_blind).length === 0) { // is empty object result = false; } let result = blind_types[blind_type_preference + "_" + width_limit]; // console.log('Func: calc_TYPE_OF_BLIND') // console.log('args: ' + blind_types + " " + blind_type_preference + " " + poletrackwidth) // console.log('return: ', result) // console.log('------END-------') return result; } /** * Calculates and returns the HEM_LENGTHREQ * @param {Float} hem_usage * @param {Float} length_req */ calc_HEM_LENGTHREQ(hem_usage, length_req) { return hem_usage + length_req; } /** * Calculates and returns the Lining Requirement (cm) * @param {Float} hem_usage * @param {Float} length_req * @param {Float} b_width_usage */ calc_BLINDS_LINING_REQ(hem_usage, length_req, b_width_usage) { // console.log('Func: calc_BLINDS_LINING_REQ') // console.log('args: ' + hem_usage + " " + length_req + " " + b_width_usage) // console.log('return: ', (hem_usage + length_req) * b_width_usage) // console.log('------END-------') return (hem_usage + length_req) * b_width_usage; } /** * Calculates and returns the Lining Cost * @param {Float} BLINDS_LINING_REQ * @param {Float} cost_of_lining */ calc_BLINDS_LINING_COST(BLINDS_LINING_REQ, cost_of_lining) { let cost = (BLINDS_LINING_REQ / 100) * cost_of_lining; let pennies = (cost * 100); let roundedPennies = Math.round(pennies); let result = roundedPennies / 100; // console.log('Func: calc_BLINDS_LINING_COST') // console.log('args: ' + BLINDS_LINING_REQ + " " + cost_of_lining) // console.log('cost: ',cost) // console.log('pennies: ',pennies) // console.log('roundedPennies: ',roundedPennies) // console.log('return: ',result) // console.log('------END-------') return result; } /** * Calculates and returns the FABRIC_USAGE * @param {Float} HEM_LENGTHREQ * @param {Float} pattern_repeat */ calc_FABRIC_USAGE(HEM_LENGTHREQ, pattern_repeat) { const result = (Math.ceil(HEM_LENGTHREQ / pattern_repeat)) * pattern_repeat; return result; } /** * Calculates and returns the BLINDS_FINAL_FABRIC_USAGE * @param {Float} b_column1 * @param {Float} b_widthMinusXGtTrackWidth */ calc_BLINDS_FINAL_FABRIC_USAGE(b_column1, b_widthMinusXGtTrackWidth) { const result = (b_column1 + b_widthMinusXGtTrackWidth) * 1.05; // console.log('Func: calc_BLINDS_FINAL_FABRIC_USAGE') // console.log('args: ' + b_column1 + " " + b_widthMinusXGtTrackWidth) // console.log('return: ', result) // console.log('------END-------') return result; } /** * Calculates and returns the BLINDS_FABRIC_COST * @param {Float} BLINDS_FINAL_FABRIC_USAGE * @param {Float} cut_price */ calc_BLINDS_FABRIC_COST(BLINDS_FINAL_FABRIC_USAGE, cut_price) { let cost = (BLINDS_FINAL_FABRIC_USAGE / 100) * cut_price; let pennies = (cost * 100); let roundedPennies = Math.round(pennies); let result = roundedPennies / 100; // console.log('Func: calc_BLINDS_FABRIC_COST') // console.log('args: BLINDS_FINAL_FABRIC_USAGE cut_price') // console.log('args: ' + BLINDS_FINAL_FABRIC_USAGE + " " + cut_price) // console.log('cost', cost); // console.log('pennies', pennies); // console.log('roundedPennies', roundedPennies); // console.log('return: ', result) // console.log('------END-------') return result; } /** * Calculates and returns the BLINDS_MAKEUP_COST * @param {Float} poletrackwidth * @param {Object} blind_makeup_costs */ calc_BLINDS_MAKEUP_COST(poletrackwidth, blind_makeup_costs) { let result = blind_makeup_costs.less_than_60; if (poletrackwidth <= 60) { return result; } if (poletrackwidth <= 90) { result = blind_makeup_costs.less_than_90; return result; } if (poletrackwidth <= 120) { result = blind_makeup_costs.less_than_120; return result; } if (poletrackwidth <= 150) { result = blind_makeup_costs.less_than_150; return result; } if (poletrackwidth <= 180) { result = blind_makeup_costs.less_than_180; return result; } if (poletrackwidth <= 210) { result = blind_makeup_costs.less_than_210; return result; } if (poletrackwidth <= 240) { result = blind_makeup_costs.less_than_240; return result; } // poletrackwidth was greater than the highest number we are checking for result = 0; return result; } /** * Calculates and returns the WIDTHS_NEEDED * @param {Float} gather * @param {Float} poletrackwidth * @param {Float} fabric_width */ calc_WIDTHS_NEEDED(gather, poletrackwidth, fabric_width) { const result = (gather * poletrackwidth) / fabric_width; return Math.ceil(result); } /** * Calculates and returns the TOTAL_FABRIC_REQ * @param {Float} FABRIC_USAGE * @param {Float} WIDTHSc_NEEDED */ calc_TOTAL_FABRIC_REQ(FABRIC_USAGE, WIDTHS_NEEDED) { return (FABRIC_USAGE * WIDTHS_NEEDED) * 1.03; } /** * Calculates and returns the LINING_REQ (cm) * @param {Float} length_req * @param {Float} hem_usage * @param {Float} WIDTHS_NEEDED */ calc_LINING_REQ(length_req, hem_usage, WIDTHS_NEEDED) { return (length_req + hem_usage) * WIDTHS_NEEDED; } /** * Calculates and returns the FABRIC_LINING_COSTS * @param {Float} TOTAL_FABRIC_REQ * @param {Float} cut_price * @param {Float} LINING_REQ * @param {Float} cost_of_lining */ calc_FABRIC_LINING_COSTS(TOTAL_FABRIC_REQ, cut_price, LINING_REQ, cost_of_lining) { const result = ((TOTAL_FABRIC_REQ / 100) * cut_price) + ((LINING_REQ / 100) * cost_of_lining); return parseFloat(result.toFixed(2)); } /** * Calculates and returns the TOTAL_COST * @param {Float} makeup_cost_per_width * @param {Float} WIDTHS_NEEDED * @param {Float} FABRIC_LINING_COSTS * @param {Float} markup */ calc_TOTAL_COST(makeup_cost_per_width, WIDTHS_NEEDED, FABRIC_LINING_COSTS, markup) { let step_one = (makeup_cost_per_width * WIDTHS_NEEDED) + FABRIC_LINING_COSTS; let step_two = step_one + 2; const result = (markup !== 0.00) ? ((step_two * markup) + step_two) : step_two; return parseFloat(result.toFixed(2)); } /** * Calculates and returns the calc_BLINDS_TOTAL_COST * @param {Float} BLINDS_LINING_COST * @param {Float} BLINDS_FABRIC_COST * @param {Float} BLINDS_COMPONENT_COST * @param {Float} BLINDS_MAKEUP_COST */ calc_BLINDS_TOTAL_COST(BLINDS_LINING_COST, BLINDS_FABRIC_COST, BLINDS_COMPONENT_COST, BLINDS_MAKEUP_COST, markup) { let cost = BLINDS_LINING_COST + BLINDS_FABRIC_COST + BLINDS_COMPONENT_COST + BLINDS_MAKEUP_COST; let pennies = (cost * 100); let roundedPennies = Math.round(pennies); const result = (markup !== 0.00) ? ((roundedPennies / 100) * markup) + (roundedPennies / 100) : (roundedPennies / 100); //console.log('calc_BLINDS_TOTAL_COST', BLINDS_LINING_COST, BLINDS_FABRIC_COST, BLINDS_COMPONENT_COST, BLINDS_MAKEUP_COST, markup) //console.log(cost, pennies, roundedPennies, (roundedPennies / 100), (roundedPennies / 100) * markup); //console.log('return: ', parseFloat(result.toFixed(2))); return parseFloat(result.toFixed(2)); } /** * Calculates and returns the calc_TOTAL_COST_AFTER_SALE_OPTION * @param {Float} sale_percent * @param {Float} total_price * @returns */ calc_TOTAL_COST_AFTER_SALE_OPTION(sale_percent, total_price) { //console.log('calc_TOTAL_COST_AFTER_SALE_OPTION'); //console.log('percent', sale_percent); //console.log('total_price', total_price) const result = total_price * ((100 - sale_percent) / 100); //console.log('return: ', parseFloat(result.toFixed(2))); return parseFloat(result.toFixed(2)); } } class FormValidator { /** * Validate if a value is a valid product type * @param {String} value - A product type */ static validateProductType(value) { const DICTIONARY = ["curtains", "blinds"]; if (!DICTIONARY.includes(value)) { return false; } return true; } /** * Validate if a value is a valid fitting * @param {String} value - A fitting option */ static validateFitting(value) { const DICTIONARY = ["recess", "exact"]; if (!DICTIONARY.includes(value)) { return false; } return true; } /** * Validate if a value is a valid control position * @param {String} value - A fitting option */ static validateControlPosition(value) { const DICTIONARY = ["left", "right"]; if (!DICTIONARY.includes(value)) { return false; } return true; } /** * Validate if a value is a valid eyelet colour * @param {String} value - A fitting option */ static validateEyeletColour(value) { const DICTIONARY = ["nickel", "antique_brass", "silver", "black"]; if (!DICTIONARY.includes(value)) { return false; } return true; } /** * Validate if a value is products path * @param {String} value - The value being tested */ static validateProductPage(value) { // /products/abc /products/abc123 /products/abc-123 /products/123 const REGEX = /^\/products\/[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$/; if (!REGEX.test(value)) { return false; } return true; } /** * Validate if a value is a float * @param {String} value - The value being tested */ static validateIsFloat(value) { const REGEX = /^\d+(\.\d{1,2})?$/; if (!REGEX.test(value)) { return false; } return true; } /** * Validate if a value is a valid curtain type * @param {String} value - A curtain type */ static validateCurtainType(value) { const DICTIONARY = ["pencil_pleat", "pinch_pleat", "eyelet"]; if (!DICTIONARY.includes(value)) { return false; } return true; } /** * Validate if a value is a valid blinds type * @param {String} value - A blinds type */ static validateBlindsType(value) { const DICTIONARY = ["roman"]; if (!DICTIONARY.includes(value)) { return false; } return true; } /** * Validate if a value is a valid pole track width * @param {String} value - Pole track width */ static validatePoleTrackWidth(value, high_limit) { if (!this.validateIsFloat(value) || !this.validateBetweenMinMax(value, 30, high_limit)) { return false; } return true; } /** * Validate if a value is a valid length req (drop) for curtains * @param {String} value - Length required */ static validateLengthReq(value) { if (!this.validateIsFloat(value) || !this.validateBetweenMinMax(value, 30, 300)) { return false; } return true; } /** * Validate if a value is a valid length req (drop) for blinds * @param {String} value - Length required */ static validateBlindsLengthReq(value) { if (!this.validateIsFloat(value) || !this.validateBetweenMinMax(value, 30, 240)) { return false; } return true; } /** * Validate if a value is a valid lining type * @param {String} value - A lining type */ static validateLiningType(value) { const DICTIONARY = ["standard", "blackout", "thermal"]; if (!DICTIONARY.includes(value)) { return false; } return true; } /** * Validate if a value contains nothing more than letters, numbers and hyphens. * @param {String} value */ static validateAlphaNumericHyphen(value) { const REGEX = /([A-Za-z0-9\-]+)/; if (!REGEX.test(value)) { return false; } return true; } /** * Validate if a value is between a given min/max value * @param {String} value * @param {Integer} min * @param {Integer} max */ static validateBetweenMinMax(value, min, max) { let parsedValue = parseFloat(value); if (isNaN(parsedValue)) { return false; } if (parsedValue < min || parsedValue > max) { return false; } return true; } /** * Combine validation funcs to perform an all in one validation * @param {Object} data */ static validateApiGenerate(data) { let result = { valid: true, errors: [] } // result.shop = this.validateAlphaNumericHyphen(data.shop); // result.product_type = this.validateProductType(data.product_type); if (data.product_type == "blinds") { result.fitting = this.validateFitting(data.fitting); result.control_position = this.validateControlPosition(data.control_position); // result.blinds_type = this.validateBlindsType(data.blinds_type); result.length_req = this.validateBlindsLengthReq(data.length_req); } else { result.curtain_type = this.validateCurtainType(data.curtain_type); //result.eyelet_colour = this.validateEyeletColour(data.eyelet_colour); result.length_req = this.validateLengthReq(data.length_req); } // result.product_page = this.validateProductPage(data.product_page); result.length_req = this.validateLengthReq(data.length_req); result.lining_type = this.validateLiningType(data.lining_type); let highWidthLimit = document.getElementById("poletrackwidth").getAttribute("max"); result.poletrackwidth = this.validatePoleTrackWidth(data.poletrackwidth, Number(highWidthLimit)); // result.total_price = this.validateIsFloat(data.total_price); for (let key in result) { if (!result[key]) { // Found an invalidation result.errors.push(`${key} is invalid`) if (result.valid) { result.valid = false; } } } return result; } } // Page Load document.addEventListener("DOMContentLoaded", function () { // Handler when the DOM is fully loaded if (document.getElementById('made-to-measure-area')) { // MTM Page detected ProductMTM.initProduct().then(productInstance => { //productInstance.showData(); }); }; // Accordian for converter var acc = document.getElementsByClassName("accordion"); var i; for (i = 0; i < acc.length; i++) { acc[i].addEventListener("click", function () { console.log("CLICKKKK!!") /* Toggle between adding and removing the "active" class, to highlight the button that controls the panel */ this.classList.toggle("active"); /* Toggle between hiding and showing the active panel */ var panel = this.nextElementSibling; if (panel.style.display === "block") { panel.style.display = "none"; } else { panel.style.display = "block"; } }); } // // selecting the radio button when clicking on the image // //Nickel // document.getElementById("nickel_image").addEventListener("click", selectNickel); // function selectNickel(){ // document.getElementById('nickel').checked = true; // } // //Nickel // document.getElementById("brass_image").addEventListener("click", selectBrass); // function selectBrass(){ // document.getElementById('antique_brass').checked = true; // } });

Sayuri Moleskin Made To Measure Curtains (1)

Sayuri Moleskin Made To Measure Curtains (2)

Sayuri Moleskin Made To Measure Curtains (3)

£42.04 SAVE £14.71

Delivered in 10-15 working days

Sayuri Moleskin Made To Measure Curtains (4)

Our standard lining is a polycotton twill lining and contains 70% polyester and 30% cotton. Our standard lining is available in ivory colour only. Choosing a standard lining will gently filter any outside light.

Sayuri Moleskin Made To Measure Curtains (5)

Our blackout lining is ideal for rooms, such as bedrooms, where darkness is required. The blackout lining is rated at 3 Pass blackout (Highest rating) and is coloured white. The lining has a polycotton base with acrylic coating.

Sayuri Moleskin Made To Measure Curtains (6)

Use a thermal lining to help keep your rooms warm in winter and cool in summer. The thermal lining has a 1 pass blackout and is coloured ivory. The lining has a polycotton base with acrylic coating.

Sayuri Moleskin Made To Measure Curtains (7)

If you want your blind to fit inside your window this is the option for you. Please note with a recess fit we will adjust your size slightly in order for your blind to fit inside your window recess correctly.

Sayuri Moleskin Made To Measure Curtains (8)

By choosing the exact fitting option your blind will be made to your exact size and no adjustments will be made. An exact fit option is often used for blinds that sit on the outside of the window when there is no recess.

Sayuri Moleskin Made To Measure Curtains (9)

All chains provided will be a maximum length of one metre in order to comply with child safety regulations. This ensures all chains and cords are kept out of reach of children.

Sayuri Moleskin Made To Measure Curtains (10)

All our roman blinds use an upgraded sidewinder chain control. The chain is enclosed within the cassette to ensure a smooth running action. No more tangled cords or draping over furniture.

Sayuri Moleskin Made To Measure Curtains (11)

3" Pencil pleat tape heading is ideal for both poles and tracks. The pencil pleat is 75mm deep (3") and comes with three hook positions. Pencil pleat curtains offer a modern simple style of curtain that gives an elegant look. This curtain will arrive flat enabling you to pull the heading tape strings to form pleats.

Sayuri Moleskin Made To Measure Curtains (12)

Choosing an eyelet heading gives you a smart contemporary look with soft folds. You are able to choose a finish for your eyelets to suit your decor. Our eyelets are 40mm in diameter and are suitable for curtain poles up to 30mm in diameter. Eyelet curtains are not suitable for curtain tracks.

Sayuri Moleskin Made To Measure Curtains (13)

3" Pinch pleat curtains have a sophisticated and serene look and include permanently sewn in pleats. Pinch pleats are suitable for both tracks and poles. Pinch pleat curtains look great in all kinds of interiors

Please wait...

Please type a valid number

Please type a valid number

Get a free sample of the fabric

Sorry, something went wrong.

Sayuri Moleskin Made To Measure Curtains (18)

Sayuri Moleskin Made To Measure Curtains (19)

  • Description
  • Additional Information
  • Delivery
  • Curtain Measuring Guide

Description

Sayuri Moleskin Made To Measure Curtains

  • Manufacturer Colour: MOLESKIN
  • Pattern Repeat: 64 cm
  • Fabric Width: 137cm
  • Fabric Weight:
  • Fabric Composition: 100% Cotton
  • Brand: Prestigious
  • Country Of Origin:
  • Fabric Finish: Printed

Curtain Details

  • Made to order and delivered in 10-15 working days.
  • Curtains are sold as a pair. If you require a single panel contact us directly.
  • Curtains wider than the fabric width will have join.
  • Choose between 3" pencil pleat, 3" pinch pleat, and eyelet curtain heading.
  • Curtains are lined with your choice of standard, thermal or blackout lining

Follow our made to measure curtain guidehere.

Additional Information

Sayuri Moleskin Made To Measure Curtains

  • Sizes Available: Made to Measure
  • Brand: Prestigious Textiles
  • Colour: Natural, Green, and Multicolour
  • Material: Cotton
  • Style: Printed
  • Product Benefits: Made to Measure

Delivery

Made to measure products are dispatched within 10 to 15 working days

United Kingdom Made to MeasureDelivery Charges

Mainland UK

Orders up to £74.99 - £3.95 Delivery
Orders £75.00 and Over -Free

Channel Islands & Isle of Man

Orders up to £29.99 - £6.95 Delivery
Orders £30.00 to £49.99 - £8.95 Delivery
Orders £50.00 and Over -£10.95 Delivery

Northern Ireland

Orders up to £29.99 - £4.95 Delivery
Orders £30.00 to £49.99 - £6.95 Delivery
Orders £50.00 and Over -£8.95 Delivery

Curtain Measuring Guide

Ideal Textiles Measuring Guides Central

Welcome to the comprehensive hub for all your measuring guide needs. At Ideal Textiles, we understand the importance of precision when fitting your windows with the perfect blinds or curtains. Our step-by-step guides are here to make the process seamless.

Choose your guide below:

Venetian BlindsSayuri Moleskin Made To Measure Curtains (20)Curtains Sayuri Moleskin Made To Measure Curtains (21)
Roller BlindsSayuri Moleskin Made To Measure Curtains (22)Ready Made CurtainsSayuri Moleskin Made To Measure Curtains (23)
Roman BlindsSayuri Moleskin Made To Measure Curtains (24)Net CurtainsSayuri Moleskin Made To Measure Curtains (25)

Need Assistance?
If you have questions or need further guidance at any stage of your measuring journey, please don't hesitate tocontact us directly. We're here to help ensure your measurements are perfect every time.

Customer Reviews

Be the first to write a review

0%

(0)

0%

(0)

0%

(0)

0%

(0)

0%

(0)

Sayuri Moleskin Made To Measure Curtains (2024)
Top Articles
Latest Posts
Article information

Author: Frankie Dare

Last Updated:

Views: 6622

Rating: 4.2 / 5 (73 voted)

Reviews: 80% of readers found this page helpful

Author information

Name: Frankie Dare

Birthday: 2000-01-27

Address: Suite 313 45115 Caridad Freeway, Port Barabaraville, MS 66713

Phone: +3769542039359

Job: Sales Manager

Hobby: Baton twirling, Stand-up comedy, Leather crafting, Rugby, tabletop games, Jigsaw puzzles, Air sports

Introduction: My name is Frankie Dare, I am a funny, beautiful, proud, fair, pleasant, cheerful, enthusiastic person who loves writing and wants to share my knowledge and understanding with you.