I looked up how to use the PubSub pattern in JS and came across a few articles. I wanted to have two way data binding, so when a one of an Object's Properties value was set, it would update x number of subscribers.
It sounds confusing, but here's the relevant sections of my code:
// data-cluckles-jumbotron="padding" adds the input to the subscribers array
// in clucklesEditor.jumbotron.padding.subscribers
// In context Jumbotron is a constructor function, padding is a property
// So this.padding = {}
<input type="number" data-cluckles-jumbotron="padding" value="30" placeholder="30" min="1" step="1" />
Here's the Jumbotron
constructor function. As you can see it inherits from ThemeModifier
s, which is where the two way data binding, and the PubSub is setup.
var Jumbotron = function (editor) {
ThemeModifier.call(this, editor); // Call parent constructor
this.subscriberDataAttribute = 'data-cluckles-jumbotron';
// Configure the Modifiers
this.bg = {
variable: '@jumbotron-bg',
subscribeProperty: 'bg',
changeFn: this.setBackgroundColor.bind(this),
subscribers: [],
_value: null
};
this.setupDataBinding();
};
// Inherit from parent Prototype and preserve constructor
Jumbotron.prototype = Object.create(ThemeModifier.prototype);
Jumbotron.prototype.constructor = Jumbotron;
Jumbotron.prototype.setBackgroundColor = function (backgroundColor) {
this.modifiers.bg.value = backgroundColor;
};
Now here's ThemeModifier
s. I have stripped out the non relevant parts:
var ThemeModifier = function (editor) {
Object.defineProperties(this, {
'editor': {
enumerable: false,
value: editor
},
'modifiers': {
enumerable: false,
writable: true,
value: {}
}
});
};
/**
* Configured the Two Way Databinding for the modifiers, which includes
* binding multiple DOM Element subscribers which have the "data-cluckles-{{type}}" attribute,
* which makes them update when the modifiers change, and changing the modifiers when the DOM
* Element's values change.
*
* Example of Two Way Databinding:
*
* editor.jumbotron.setBackgroundColor('#000000'); // Will Update the modifier and all Subscribers
* <input type="color" data-cluckles-jumbotron="bg" /> // Will Update the modifier and all Subscribers
*
* @returns {undefined}
*/
ThemeModifier.prototype.setupDataBinding = function () {
var editor = this.editor, // ClucklesEditor instance
// DOM Element Subscribers // *[data-cluckles-{{type}}] e.g. *[data-cluckles-jumbotron]
subscribers = Array.prototype.slice.call(document.querySelectorAll('*[' + this.subscriberDataAttribute + ']'));
// Setup the value accessors, and configure them to Notify the subscribers of changes
Object.keys(this.modifiers).forEach(function(modifierName) {
var modifier = this.modifiers[modifierName];
// If a value property has already been defined, we dont need to attach
// a generic value accessor methods
if (!modifier.hasOwnProperty('value')) {
// Define getters/setters to change the value, apply it, and notify subscribers
Object.defineProperty(modifier, 'value', {
get: function () { return this._value; },
set: function (val) {
var unit = 'px'; // Default unit to append (px, em, rem, etc)
// If this property requires a suffix unit
if (this.suffixUnit) {
// Store the raw value
this._rawValue = val;
// If a custom unit is specified
if (this.unit) {
// Set the unit to append
unit = this.unit;
}
// Combine the value with the unit
this._value = val + unit;
} else {
// Store the new value
this._value = val;
}
// Queue the modifications to be applied by less
editor.queueModifications();
// Notify each of the Subscribers of the value change
this.subscribers.forEach(function (subscriber) {
subscriber.value = val;
});
}
});
}
}, this);
// Store the Subscribers, and setup their 'change' listeners
subscribers.forEach(function (subscriber) {
// Get the data attribute which should match the subscribeProperty of a modifier
// which it wants to bind to
var subscribeToProperty = subscriber.getAttribute(this.subscriberDataAttribute);
Object.keys(this.modifiers).forEach(function (modifierName) {
// Get the modifier object
var modifier = this.modifiers[modifierName];
// If this modifiers handles the property we want to subscribe to
if (modifier.subscribeProperty === subscribeToProperty) {
// Store the subscriber for this modifier
modifier.subscribers.push(subscriber);
// Add a change event which will call the change function and pass
// through the value of the DOM Element
subscriber.addEventListener('change', function (e) {
var suffixUnit = e.target.getAttribute('data-cluckles-unit');
// If the DOM Element has a "unit" data binding
if (suffixUnit) {
// Call the change function and provide the extra suffix
modifier.changeFn(e.target.value, suffixUnit);
} else {
// else call change function as default
modifier.changeFn(e.target.value);
}
}, false);
}
}, this);
}, this);
};
All inputs which have data-cluckles-jumbotron="PROPERTY"
will automatically be added to the subscribers
array of the property which match PROPERTY (e.g. bg).
Then when the inputs value changes, that change is sent to the changeFn
e.g. Jumbotron#setBackgroundColor
.
This means that you can also do the same thing from the console.
In ThemeModifier
I have add an Object.defineProperty
block, which handles when the value is set and automatically sets the value of each of the inputs in the subscribers
array of the associated property.
The full source for this is from my project Cluckles on Github and to see how it works in action you can go to cluckles.com.
It makes sense to me and seems to work well, but I'm not sure if it's the best way to do this? I'm looking for feedback on if this is an effective way to do what I'm trying to achieve.