<div class="parent-component">
  <label for="pennies">Pennies</label>
  <br/>
  <div class="pennies-container">
    <input type="range" id="pennies" name="pennies" value="0" min="0" max="100"/>
    <span id="pennies-count">0</span>
  </div>
  
  <div class="child-component">
    <label>Quarters</label>
    <br/>
    <input type="text" id="quarters" name="quarters" value="0" disabled/>
  </div>
  
  <div class="child-component">
    <label>Dollar</label>
    <br/>
    <input type="checkbox" id="dollar" name="dollar" value="0" disabled/>
  </div>
  
  <div class="child-component">
    <p id="rich">I'M RICH!!!</p>
  </div>
</div>
* {
  box-sizing: border-box;
  font-family: Arial;
}

.parent-component, .child-component {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.parent-component label {
  font-size: 2rem;
}

.child-component {
  margin-top: 2em;
}

.pennies-container {
  display: flex;
  justify-content: center;
  align-items: center;
}

#pennies-count {
  display: inline-block;
  width: 1rem;
  margin-left: 1rem;
}

#rich {
  display: none;
  font-size: 4rem;
  margin: 0;
  color: green;
}
// clear CodePen console
console.clear();

// query DOM
const pennies = document.getElementById('pennies');
const penniesCount = document.getElementById('pennies-count');
const quarters = document.getElementById('quarters');
const dollar = document.getElementById('dollar');
const rich = document.getElementById('rich');

// initiate state
let state = {
  pennies: 0,
  quarters: 0,
  dollar: false,
  rich: false
};

// define depend and notify methods
class Dep {
  constructor() {
    // use Set to mitigate duplicates
    this.subscribers = new Set();
  }
  
  depend() {
    if (activeUpdate) {
      // register current activeUpdate as a subscriber
      this.subscribers.add(activeUpdate);
    }
  }
  
  notify() {
    // run all subscriber functions
    // invoked in insertion order
    this.subscribers.forEach(sub => sub());
  }
};

// define the addition of getters and setters to obj argument (state)
const observe = (obj) => {
  Object.keys(obj).forEach(key => {
    let stateValue = obj[key];
    const dep = new Dep();
    
    Object.defineProperty(obj, key, {
      get() {       
        dep.depend();
        return stateValue;
      },
      set(newValue) {
        const hasChanged = stateValue !== newValue;
        if (hasChanged) {
          stateValue = newValue;
          dep.notify();
        }
      }
    });
    
  });
};

// decorate state with getters and setters
observe(state);

// when updateOnStateChange invokes and in turn invokes a state getter,
// then the wrappedUpdate function is added to its subscriber Set
let activeUpdate = null;
const updateOnStateChange = update => {
  function wrappedUpdate() {
    activeUpdate = wrappedUpdate;
    update();
    activeUpdate = null;
  }
  
  wrappedUpdate();
};

// add event listener to range input
pennies.addEventListener('input', e => {
  state.pennies = e.target.value;
  penniesCount.innerText = state.pennies;
});

// pennies getter, below, adds wrappedUpdate to its subscriber Set
// which contains the updateOnStateChange callback below.
// now when pennies input event listener fires, 
// this callback is invoked by pennies setter.
updateOnStateChange(() => {
  if (state.pennies < 25) {
    state.quarters = 0;
  } else if (state.pennies < 50) {
    state.quarters = 1;
  } else if (state.pennies < 75) {
    state.quarters = 2;
  } else if (state.pennies < 100) {
    state.quarters = 3;
  } else {
    state.quarters = 4;
  }

  quarters.value = state.quarters;
});

// quarters getter adds wrappedUpdate to its subscriber Set
// which contains the updateOnStateChange callback below.
// now when quarters setter is called above,
// this callback is invoked
updateOnStateChange(() => {
  if (state.quarters === 4) {
    state.dollar = true;
  } else {
    state.dollar = false;
  }
  
  dollar.checked = state.dollar;
});

// dollar getter adds wrappedUpdate to its subscriber Set
// which contains the updateOnStateChange callback below.
// now when dollar setter is called above,
// this callback is invoked
updateOnStateChange(() => {
  if (state.dollar) {
    state.rich = true;
    rich.style.display = 'block';
  } else {
    state.rich = false;
    rich.style.display = 'none';
  }
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.