Appearance
Checkout JavaScript Widget
SmartCut Checkout is an embedded JavaScript widget that provides a complete cut calculator for your e-commerce or quotation website.
Overview
SmartCut Checkout allows you to:
- Embed a cutting calculator directly on your website
- Calculate prices in real-time on the client side
- Filter products in your catalogue
- Customize the UI to match your brand
- Support multiple materials, extras, and configurations
- No server-side integration required
Quick Start
Installation
Include the script and default css styling in your HTML:
html
<link rel="stylesheet" href="https://cutlistevo.com/checkout/checkout.css" />
<script type="module" src="https://cutlistevo.com/checkout/checkout.js"></script>Basic Usage
html
<!-- Container for the calculator -->
<div id="smartcut-app"></div>
<script>
// Configuration
const initData = {
stock: [
{
l: 2440,
w: 1220,
t: 18,
cost: 65,
material: 'Plywood'
}
],
saw: {
stockType: 'sheet',
bladeWidth: 3.2,
cutType: 'efficiency',
cutPreference: 'l'
},
options: {
locale: 'en-US',
currency: 'USD'
}
}
// Wait for the ready event and then call init
window.addEventListener('smartcut/ready', () => {
window.smartcutCheckout.init(initData)
})
// Listen for calculation events
window.addEventListener('smartcut/calculating', () => {
console.log('Calculation started')
})
// Listen for results
window.addEventListener('smartcut/result', (e) => {
const result = e.detail
console.log('Optimization complete:', result)
console.log('Total stock cost:', result.checkout.formattedTotalStockCost)
})
</script>Configuration
Stock Configuration
Define the available stock materials:
javascript
const initData = {
stock: [
{
l: 2440,
w: 1220,
t: 18,
cost: 65,
material: 'Plywood'
}
],
saw: {
stockType: 'sheet',
bladeWidth: 3.2,
cutPreference: 'l'
},
options: {
locale: 'en-US',
currency: 'USD',
apiVersion: 3 // 2 or 3 (default: 3)
}
}Saw Options
Saw configuration should be provided in the saw property (recommended) or in options for backward compatibility.
Complete Saw Configuration
javascript
saw: {
stockType: 'sheet', // 'sheet', 'linear', or 'roll'
bladeWidth: 3.2, // Blade width in mm
cutType: 'guillotine', // 'efficiency', 'guillotine', 'beam', 'none'
cutPreference: 'l', // 'l' (length), 'w' (width), 'none'
stackHeight: 60, // Maximum stack height in mm (optional)
guillotineOptions: { // Optional guillotine settings
headCuts: true,
strategy: 'efficiency', // 'efficiency' or 'time'
maxPhase: 3
}
}Cut Type Values:
'efficiency'- Standard efficiency optimization'guillotine'- Guillotine cutting (only straight cuts across entire stock)'beam'- Beam saw optimization
Cut Preference Values:
'l'- Prefer length-first cuts'w'- Prefer width-first cuts
Configuration Options
API Version
javascript
options: {
apiVersion: 3 // 2 or 3 (default: 3)
}API Version Differences:
- Version 2: Uses flat extras structure (
{ banding: { x1: string, x2: string, y1: string, y2: string } }) - Version 3: Uses nested extras structure (
{ extras: { banding: { sides: { l1: string, l2: string } } } })
See Result Format for details on the differences between versions.
Emit API Result
javascript
options: {
emitAPIResult: true // Include full API v3 response in result (default: false)
}When enabled, the result object will include an apiResultV3 property containing the complete API v3 response format. This is useful when you need the standardized API response structure regardless of which apiVersion is configured for the checkout result.
See API Result V3 in the Result Format section for the structure.
Number Format
javascript
options: {
numberFormat: 'decimal', // 'decimal' or 'fraction'
decimalPlaces: 2,
fractionRoundTo: 0 // For fraction formatting
}Orientation Model
javascript
options: {
orientationModel: 0, // 0, 1, or 2 (input parts)
resultOrientationModel: 0 // 0, 1, or 2 (result parts)
}Orientation Model Values:
0- No orientation preference1- Length-preferred orientation2- Width-preferred orientation
Custom Fields
javascript
options: {
customFields: [
{
type: 'string',
label: 'Project Name',
id: 'projectName',
placeholder: 'Enter project name'
},
{
type: 'integer',
label: 'Quantity',
id: 'quantity',
default: 1
},
{
type: 'select',
label: 'Finish',
id: 'finish',
outputType: 'string',
options: [
{ label: 'Matt', value: 'matt' },
{ label: 'Gloss', value: 'gloss' }
]
},
{
type: 'checkbox',
label: 'Rush Order',
id: 'rush',
trueValue: 'yes',
falseValue: 'no',
default: 'no'
}
]
}UI Customization
Control colors, enabled features, and field order:
javascript
options: {
enable: {
banding: true,
finish: true,
planing: true,
machining: true,
diagram: true,
orientation: true,
csvImport: false,
partName: true,
focus: false,
click: true
},
colors: {
partA: '#118ab2',
stock: '#ffd166',
button: '#118ab2',
buttonText: '#ffffff',
text: '#000000'
}
}Extras Configuration
Extras are additional services like edge banding, finishing, planing, and machining that can be applied to parts with custom pricing.
Basic Extras Setup
javascript
const initData = {
stock: [...],
// Edge banding configuration
banding: {
labels: ['type', 'thickness'],
pricing: {
'oak|1mm': 1.0,
'oak|2mm': 1.1,
'pine|1mm': 2.0,
'maple|1mm': 3.0
}
},
// Finish configuration
finish: {
labels: ['type', 'style'],
pricing: {
'spray|matt': 1.0,
'spray|satin': 1.1,
'lacquer|matt': 2.0,
'lacquer|satin': 2.2
}
},
// Planing configuration
planing: {
labels: ['type'],
pricing: {
'standard': 0.5,
'premium': 0.8
}
},
options: {...}
}Extras Location Filtering
Control which sides and faces are available for each extra type by adding a locations property directly to each extra configuration:
javascript
// Available sides: 'side.l1', 'side.l2', 'side.w1', 'side.w2' (main edges)
// 'side.a', 'side.b', 'side.c', 'side.d' (corners)
// Available faces: 'face.a', 'face.b'
banding: {
labels: ['material', 'thickness'],
pricing: {...},
locations: ['side.l1', 'side.l2', 'side.w1', 'side.w2'] // Only main edges
},
finish: {
labels: ['type'],
pricing: {...},
locations: ['face.a', 'face.b'] // Both faces
},
planing: {
labels: ['type'],
pricing: {...},
locations: ['side.w1', 'side.w2', 'face.a', 'face.b'] // Edges and faces
}Extras Location Groups
Create custom groupings of locations for bulk operations with optional pricing by adding a groups array directly to each extra configuration:
javascript
planing: {
labels: ['type'],
pricing: {
'standard': 0.5,
'premium': 0.8
},
locations: ['side.w1', 'side.w2', 'face.a', 'face.b'],
groups: [
{
id: 'two-sided',
label: '2 sided',
locations: ['face.a', 'face.b'],
price: 100, // Optional: fixed price for this group
hideIndividualLocations: true // Optional: hide individual location controls when group is available
},
{
id: 'four-sided',
label: '4 sided',
locations: ['face.a', 'face.b', 'side.w1', 'side.w2'],
price: 200,
hideIndividualLocations: true
}
]
},
banding: {
labels: ['material'],
pricing: {...},
groups: [
{
id: 'all-corners',
label: 'All Corners',
locations: ['side.a', 'side.b', 'side.c', 'side.d']
},
{
id: 'long-sides',
label: 'Long Sides',
locations: ['side.l1', 'side.l2']
}
]
}Group Properties:
id(required) - Unique identifier for the grouplabel(required) - Display name shown in the UIlocations(required) - Array of location strings included in this groupprice(optional) - Fixed price for the entire group (overrides individual location pricing)hideIndividualLocations(optional) - Whentrue, hides the individual location controls for locations in this group
Extras Validation Rules
Apply dimension constraints to control which parts can have extras applied by adding a rules property directly to each extra configuration:
javascript
planing: {
labels: ['type'],
pricing: {...},
locations: [...],
rules: {
t: {
min: 8,
max: 230
},
shortSide: {
min: 10,
max: 620
},
longSide: {
max: 1000
},
message: 'Planing requires thickness 8-230mm, short side 10-620mm, and long side max 1000mm'
}
},
banding: {
labels: ['material'],
pricing: {...},
rules: {
t: {
min: 12,
max: 30
},
message: 'Banding is only available for 12-30mm thick materials'
}
},
finish: {
labels: ['type'],
pricing: {...},
rules: {
longSide: {
max: 2400
},
shortSide: {
max: 1200
},
message: 'Finish is only available for parts smaller than 2400x1200mm'
}
}Validation Rule Fields:
longSide.min/longSide.max- Constraints on the longer dimensionshortSide.min/shortSide.max- Constraints on the shorter dimensiont.min/t.max- Thickness constraintsformula(optional) - JavaScript expression for complex validation (can referencelongSide,shortSide,t)message- Custom error message shown when validation fails
When a part doesn't meet the validation rules, the extras options will be disabled and an error message displayed.
Part Validation Rules
Apply dimension constraints to parts themselves to control which parts can be processed by adding a partRules property to the init data:
javascript
const initData = {
stock: [...],
// Part validation rules
partRules: {
// Basic dimension constraints
l: {
min: 50,
max: 2400
},
w: {
min: 20,
max: 1200
},
t: {
min: 12,
max: 30
},
longSide: {
min: 100,
max: 2400
},
shortSide: {
min: 50,
max: 1200
},
// Cross-dimensional rule: at least one side must meet primaryMin,
// and the other side must meet secondaryMin
crossDimensionalRule: {
primaryMin: 50,
secondaryMin: 20
},
// Optional custom message
message: 'At least one side must be ≥ 50 mm and the other side must be ≥ 20 mm'
},
options: {...}
}Part Validation Rule Fields:
l.min/l.max- Length constraintsw.min/w.max- Width constraintst.min/t.max- Thickness constraintslongSide.min/longSide.max- Constraints on the longer dimensionshortSide.min/shortSide.max- Constraints on the shorter dimensioncrossDimensionalRule(optional) - Cross-dimensional validationprimaryMin- At least one side must be ≥ this valuesecondaryMin- The other side must be ≥ this value
formula(optional) - Formula expression for complex validation (can referencel,w,t,longSide,shortSide)- Supports arithmetic operators:
+,-,*,/ - Supports comparison operators:
>,<,>=,<=,== - Supports logical operators:
&&(AND),||(OR) - Supports ternary operator:
condition ? trueValue : falseValue
- Supports arithmetic operators:
message- Custom error message shown when validation fails
Example with formula:
javascript
partRules: {
formula: '(l >= 50 && w >= 20) || (w >= 50 && l >= 20)',
message: 'At least one side must be ≥ 50 mm and the other side must be ≥ 20 mm'
}Complex formula example:
javascript
partRules: {
formula: '(l * w) > 100000 && t >= 12',
message: 'Part must have area > 100,000 mm² and thickness ≥ 12 mm'
}When a part doesn't meet the validation rules, an error message will be displayed below the part and calculation will be blocked until the issue is resolved.
Complete Extras Example
javascript
const initData = {
stock: [
{
l: 2440,
w: 1220,
t: 18,
cost: 65,
material: 'Plywood'
}
],
// Edge banding
banding: {
labels: ['material', 'thickness'],
pricing: {
'oak|1mm': 1.0,
'oak|2mm': 1.2,
'pine|1mm': 0.8,
'maple|1mm': 1.5
},
locations: ['side.l1', 'side.l2', 'side.w1', 'side.w2'],
rules: {
t: { min: 12, max: 30 },
message: 'Banding only available for 12-30mm thick materials'
}
},
// Finishing
finish: {
labels: ['type', 'style'],
pricing: {
'spray|matt': 5.0,
'spray|satin': 5.5,
'lacquer|matt': 8.0,
'lacquer|satin': 8.5
},
locations: ['face.a', 'face.b'],
rules: {
longSide: { max: 2400 },
shortSide: { max: 1200 },
message: 'Finish is only available for parts smaller than 2400x1200mm'
}
},
// Planing
planing: {
labels: ['type'],
pricing: {
'standard': 2.0,
'premium': 3.5
},
locations: ['face.a', 'face.b', 'side.w1', 'side.w2'],
groups: [
{
id: 'two-sided',
label: 'Two Sides (Top & Bottom)',
locations: ['face.a', 'face.b'],
price: 100,
hideIndividualLocations: true
},
{
id: 'four-sided',
label: 'Four Sides (All Faces & Edges)',
locations: ['face.a', 'face.b', 'side.w1', 'side.w2'],
price: 200,
hideIndividualLocations: true
}
],
rules: {
t: { min: 8, max: 230 },
shortSide: { min: 10, max: 620 },
longSide: { max: 1000 },
message: 'Planing requires: thickness 8-230mm, width 10-620mm, length max 1000mm'
}
},
saw: {
stockType: 'sheet',
bladeWidth: 3.2,
cutType: 'guillotine',
cutPreference: 'l'
},
options: {
locale: 'en-US',
currency: 'USD',
apiVersion: 3, // 2 or 3 (default: 3)
// Enable extras features
enable: {
banding: true,
finish: true,
planing: true,
machining: false
}
}
}Machining Configuration
Advanced machining features for holes, corners, and custom operations:
javascript
const initData = {
machining: {
faces: {
enabled: true
},
holes: {
enabled: true,
defaultDiameter: 5,
minDiameter: 5,
maxDiameter: 10,
enableDepth: true,
minDepth: 5
},
hingeHoles: {
enabled: true,
minimumHoleDistance: 10,
defaultDistanceFromEdge: 22,
defaultOuterSpacing: 10,
defaultHingeLength: 50
},
shelfHoles: {
enabled: true,
diameters: [5, 10]
},
corners: {
enabled: true,
types: ['radius', 'bevel'],
enableBanding: true
}
},
options: {
enable: {
machining: true
}
}
}Events
The Checkout API dispatches custom window events for integration:
smartcut/ready
Fired when the calculator is loaded and ready to initialize.
javascript
window.addEventListener('smartcut/ready', () => {
window.smartcutCheckout.init(initData)
})smartcut/initComplete
Fired when initialization is complete.
javascript
window.addEventListener('smartcut/initComplete', () => {
console.log('Calculator initialized')
})smartcut/calculating
Fired when a calculation starts.
javascript
window.addEventListener('smartcut/calculating', () => {
console.log('Calculation in progress...')
})smartcut/result
Fired when optimization results are available.
javascript
window.addEventListener('smartcut/result', (e) => {
const result = e.detail
// Optimization metadata
console.log(result.metadata)
// Parts used in optimization
console.log(result.parts)
// Stock sheets used
console.log(result.stock)
// Formatted pricing with currency
console.log(result.checkout.formattedTotalStockCost)
console.log(result.checkout.formattedBandingCost)
console.log(result.checkout.formattedFinishCost)
})smartcut/validationError
Fired when input validation fails.
javascript
window.addEventListener('smartcut/validationError', () => {
console.log('Please check your inputs')
})Result Format
The result object returned via the smartcut/result event contains different structures based on the apiVersion setting.
API Version 2 Result Structure
When apiVersion: 2 is set, results use a flat extras structure:
typescript
{
jobId: number,
metadata: {
totalStockCost: number,
bandingLengthByType: Record<string, number>,
finishAreaByType: Record<string, number>,
planingAreaByType: Record<string, number>,
addedPartTally: Record<string, number>,
usedStockTally: Record<string, number>,
unplacedParts: Array<Part>
},
parts: Array<{
l: number,
w: number,
t: number | null,
q: number,
material: string | null,
name: string | null,
orientationLock: '' | 'l' | 'w' | null,
// Flat extras structure (API v2)
banding?: Record<string, string | boolean>, // e.g., { x1: 'oak|1mm', x2: 'oak|1mm', y1: '', y2: '' }
finish?: Record<string, string | boolean>, // e.g., { a: true, b: false }
planing?: Record<string, string | boolean>,
customData?: Record<string, any>
}>,
stock: Array<{
id: string,
name: string | null,
l: number,
w: number,
t: number | null,
material: string | null,
q: number,
trim?: { x1: number, x2: number, y1: number, y2: number },
cost?: number,
// Analysis is aggregated across all stock with same parentId when stacking
// Most values are summed; areaEfficiency is averaged
analysis?: {
areaEfficiency: number, // Average efficiency across all stacked stock
finishArea: number, // Total finish area
bandingLength: number, // Total banding length
partArea: number, // Total part area
totalParts: number, // Total number of parts
stackedNumberOfCuts: number,
numberOfCuts: number,
stackedCutLength: number,
cutLength: number,
rollLength: number
},
customData?: Record<string, any>
}>,
offcuts: Array<{
l: number,
w: number,
t: number | null,
q: number,
stockId: string
}>,
inputs: {
parts: Array<Part> // Same structure as parts above
}
}API Version 3 Result Structure
When apiVersion: 3 is set (default), results use a nested extras structure:
typescript
{
jobId: number,
metadata: {
totalStockCost: number,
bandingLengthByType: Record<string, number>,
finishAreaByType: Record<string, number>,
planingAreaByType: Record<string, number>,
addedPartTally: Record<string, number>,
usedStockTally: Record<string, number>,
unplacedParts: Array<Part>
},
parts: Array<{
l: number,
w: number,
t: number | null,
q: number,
material: string | null,
name: string | null,
orientationLock: '' | 'l' | 'w' | null,
// Nested extras structure (API v3)
extras?: {
banding?: {
sides: Record<string, string | boolean> // e.g., { l1: 'oak|1mm', l2: 'oak|1mm', w1: '', w2: '' }
},
finish?: {
faces: Record<string, string | boolean> // e.g., { a: true, b: false }
},
planing?: {
sides?: Record<string, string | boolean>,
faces?: Record<string, string | boolean>
}
},
customData?: Record<string, any>
}>,
stock: Array<{
id: string,
name: string | null,
l: number,
w: number,
t: number | null,
material: string | null,
q: number,
trim?: { l1: number, l2: number, w1: number, w2: number }, // Note: uses l1/l2/w1/w2 in v3
cost?: number,
// Analysis is aggregated across all stock with same parentId when stacking
// Most values are summed; areaEfficiency is averaged
analysis?: {
areaEfficiency: number, // Average efficiency across all stacked stock
finishArea: number, // Total finish area
bandingLength: number, // Total banding length
partArea: number, // Total part area
totalParts: number, // Total number of parts
stackedNumberOfCuts: number,
numberOfCuts: number,
stackedCutLength: number,
cutLength: number,
rollLength: number
},
// Additional v3 fields
color?: { hex: string, name: string } | string,
weight?: number,
imageUrl?: string,
tags?: string[],
available?: boolean,
customData?: Record<string, any>
}>,
offcuts: Array<{
l: number,
w: number,
t: number | null,
q: number,
stockId: string
}>,
inputs: {
parts: Array<Part> // Same structure as parts above
}
}Using Result Data
javascript
window.addEventListener('smartcut/result', (e) => {
const result = e.detail
// Get total cost from metadata
const totalCost = result.metadata.totalStockCost
// Get parts with optimization data
const optimizedParts = result.parts
// Get stock usage
const stockUsed = result.stock
// Get offcuts for reuse
const offcuts = result.offcuts
// Access extras based on API version
const apiVersion = 3 // Your configured version
result.parts.forEach(part => {
if (apiVersion === 2) {
// API v2: Flat structure
const bandingX1 = part.banding?.x1
const finishFaceA = part.finish?.a
} else {
// API v3: Nested structure
const bandingL1 = part.extras?.banding?.sides?.l1
const finishFaceA = part.extras?.finish?.faces?.a
}
})
// Get extras costs breakdown from metadata
const bandingCosts = result.metadata.bandingLengthByType
const finishCosts = result.metadata.finishAreaByType
const planingCosts = result.metadata.planingAreaByType
})API Result V3
When the emitAPIResult option is enabled, the result object includes an apiResultV3 property containing the complete API v3 response. This provides a standardized format matching the SmartCut API v3 specification:
typescript
{
// Standard checkout result properties...
jobId: number,
metadata: {...},
parts: [...],
stock: [...],
offcuts: [...],
inputs: {...},
// Additional API v3 response (when emitAPIResult: true)
apiResultV3?: {
jobId: number,
saw: {
stockType: string,
bladeWidth: number,
cutType: string,
cutPreference: string
},
stockList: Array<{...}>,
shapeList: Array<{...}>,
cutList: Array<{...}>,
offcuts: Array<{...}>,
unusableShapes: Array<{...}>,
metadata: {...}
}
}This is useful when integrating with systems that expect the full API v3 response format, or when you need access to additional data like cutList that isn't included in the standard checkout result.
javascript
window.addEventListener('smartcut/result', (e) => {
const result = e.detail
// Access the API v3 response if available
if (result.apiResultV3) {
console.log('Cut list:', result.apiResultV3.cutList)
console.log('Shape list:', result.apiResultV3.shapeList)
}
})Product Filtering
Enable dynamic stock selection with filtering and search:
javascript
const initData = {
stock: [
{
l: 2800,
w: 2070,
t: 18,
cost: 95,
material: 'MDF',
name: 'White MDF 2800×2070×18mm',
category: 'Sheet Materials',
tags: ['mdf', 'white', 'standard'],
available: true
}
],
stockFilter: {
enabled: true,
config: {
displayMode: 'grid', // 'grid' or 'list'
enableSearch: true,
itemsPerPage: 20,
allowMultipleSelection: true,
availableFilters: [
{
field: 'material',
type: 'multiselect',
label: 'Material'
},
{
field: 't',
type: 'range',
label: 'Thickness (mm)',
min: 0,
max: 30
},
{
field: 'category',
type: 'multiselect',
label: 'Category'
}
]
}
},
options: {...}
}Support
- Email: hello@cutrevolution.com
- Chat: https://smartcut.dev/