diff --git a/README.md b/README.md index 6f69991..b0926d4 100755 --- a/README.md +++ b/README.md @@ -854,6 +854,8 @@ This is the main way of talking to your generated config page. An instance of th |----------|------|-------------| | `.EVENTS.BEFORE_BUILD` | String | Dispatched prior to building the page. | | `.EVENTS.AFTER_BUILD` | String | Dispatched after building the page. | +| `.EVENTS.BEFORE_DESTROY` | String | Dispatched prior to destroying the page. | +| `.EVENTS.AFTER_DESTROY` | String | Dispatched after destroying the page. | | `.config` | Array | Reference to the config passed to the constructor and used for generating the page. | | `.meta` | Object | Contains information about the current user and watch | | `.meta.activeWatchInfo` | watchinfo\|null | An object containing information on the currently connected Pebble smartwatch or null if unavailable. Read more [here](https://developer.pebble.com/docs/js/Pebble/#getActiveWatchInfo). | @@ -871,7 +873,8 @@ This is the main way of talking to your generated config page. An instance of th | `.getItemById( [string] id )` | `ConfigItem\|undefined` - a single `ConfigItem` that has the provided `id`, otherwise `undefined`. | | `.getItemsByType( [string] type )` | `Array.` - an array of config items that match the provided `type`. | | `.serialize()` | `Object` - an object representing all items with an `appKey` where the key is the `appKey` and the value is an object with the `value` property set to the result of running `.get()` on the Clay item. If the Clay item has a `precision` property set, it is included in the object | -| `.build()`
Builds the config page. Will dispatch the `BEFORE_BUILD` event prior to building the page, then the `AFTER_BUILD` event once it is complete. | `ClayConfig` | +| `.build()`
Builds the config page. Will dispatch the `BEFORE_BUILD` event prior to building the page, then the `AFTER_BUILD` event once it is complete. If the config page has already been built, then the `ClayConfig.destroy()` method will be executed prior to building the page again. | `ClayConfig` | +| `.destroy()`
Destroys the config page. Will dispatch the `BEFORE_DESTROY` event prior to destroying the page, then the `AFTER_DESTROY` event once it is complete. This method wipes the config page completely, including all existing items. You will need to make sure that you re-attach your event handlers for any items that are replaced | `ClayConfig` | | `.on( [string] events, [function] handler )`
Register an event to the provided handler. The handler will be called with this instance of `ClayConfig` as the context. If you wish to register multiple events to the same handler, then separate the events with a space | `ClayConfig` | | `.off( [function] handler )`
Remove the given event handler. **NOTE:** This will remove the handler from all registered events. | `ClayConfig` | | `.trigger( [string] name, [object] eventObj={} )`
Trigger the provided event and optionally pass extra data to the handler. | `ClayConfig` | diff --git a/dev/config-2.js b/dev/config-2.js new file mode 100644 index 0000000..37d60e4 --- /dev/null +++ b/dev/config-2.js @@ -0,0 +1,38 @@ +'use strict'; +/* eslint-disable quotes */ + +module.exports = [ + { + "type": "heading", + "id": "main-heading", + "defaultValue": "Clay Test Page", + "size": 1 + }, + { + "type": "text", + "defaultValue": "This is config 2" + }, + { + "type": "section", + "items": [ + { + "type": "heading", + "defaultValue": "This is a section" + }, + { + "type": "input", + "appKey": "email", + "defaultValue": "", + "label": "Input Field", + "description": "This is a new field", + "attributes": { + "placeholder": "Placeholder set with attributes" + } + } + ] + }, + { + "type": "submit", + "defaultValue": "Save" + } +]; diff --git a/dev/custom-fn.js b/dev/custom-fn.js index fe004aa..d6202a4 100644 --- a/dev/custom-fn.js +++ b/dev/custom-fn.js @@ -16,10 +16,20 @@ module.exports = function() { } } + /** + * @returns {void} + */ + function handleButtonClick() { + Clay.config = Clay.meta.userData.config2; + Clay.build(); + } + Clay.on(Clay.EVENTS.AFTER_BUILD, function() { var coolStuffToggle = Clay.getItemByAppKey('cool_stuff'); toggleBackground.call(coolStuffToggle); coolStuffToggle.on('change', toggleBackground); + + Clay.getItemById('testButton').on('click', handleButtonClick); }); console.log('userData: ', Clay.meta.userData); diff --git a/dev/dev.js b/dev/dev.js index 72cccee..502edd2 100644 --- a/dev/dev.js +++ b/dev/dev.js @@ -6,7 +6,10 @@ window.claySettings = {}; window.customFn = require('./custom-fn.js'); window.clayComponents = require('../src/scripts/components'); window.clayMeta = require('../test/fixture').meta({ - userData: {foo: 'bar'} + userData: { + foo: 'bar', + config2: require('./config-2') + } }); var platform = window.navigator.userAgent.match(/Android/) ? 'android' : 'ios'; diff --git a/src/scripts/lib/clay-config.js b/src/scripts/lib/clay-config.js index e809876..b2ab0e0 100644 --- a/src/scripts/lib/clay-config.js +++ b/src/scripts/lib/clay-config.js @@ -33,10 +33,22 @@ function ClayConfig(settings, config, $rootContainer, meta) { var self = this; var _settings = _.copyObj(settings); - var _items = []; - var _itemsById = {}; - var _itemsByAppKey = {}; - var _isBuilt = false; + var _items; + var _itemsById; + var _itemsByAppKey; + var _isBuilt; + + /** + * Initialize the item arrays and objects + * @private + * @return {void} + */ + function _initializeItems() { + _items = []; + _itemsById = {}; + _itemsByAppKey = {}; + _isBuilt = false; + } /** * Add item(s) to the config @@ -97,6 +109,7 @@ function ClayConfig(settings, config, $rootContainer, meta) { } self.meta = meta; + self.$rootContainer = $rootContainer; self.EVENTS = { /** @@ -111,7 +124,21 @@ function ClayConfig(settings, config, $rootContainer, meta) { * value set * @const */ - AFTER_BUILD: 'AFTER_BUILD' + AFTER_BUILD: 'AFTER_BUILD', + + /** + * Called if .build() is executed after the page has already been built and + * before the existing content is destroyed + * @const + */ + BEFORE_DESTROY: 'BEFORE_DESTROY', + + /** + * Called if .build() is executed after the page has already been built and after + * the existing content is destroyed + * @const + */ + AFTER_DESTROY: 'AFTER_DESTROY' }; utils.updateProperties(self.EVENTS, {writable: false}); @@ -172,18 +199,40 @@ function ClayConfig(settings, config, $rootContainer, meta) { // @todo maybe don't do this and force the static method self.registerComponent = ClayConfig.registerComponent; + /** + * Empties the root container + * @returns {ClayConfig} + */ + self.destroy = function() { + var el = $rootContainer[0]; + self.trigger(self.EVENTS.BEFORE_DESTROY); + while (el.firstChild) { + el.removeChild(el.firstChild); + } + _initializeItems(); + self.trigger(self.EVENTS.AFTER_DESTROY); + return self; + }; + /** * Build the config page. This must be run before any of the get methods can be run + * If you call this method after the page has already been built, teh page will be + * destroyed and built again. * @returns {ClayConfig} */ self.build = function() { + if (_isBuilt) { + self.destroy(); + } self.trigger(self.EVENTS.BEFORE_BUILD); - _addItems(config, $rootContainer); + _addItems(self.config, $rootContainer); _isBuilt = true; self.trigger(self.EVENTS.AFTER_BUILD); return self; }; + _initializeItems(); + // attach event methods ClayEvents.call(self, $rootContainer); diff --git a/test/spec/lib/clay-config.js b/test/spec/lib/clay-config.js index dba8b34..80d57a9 100644 --- a/test/spec/lib/clay-config.js +++ b/test/spec/lib/clay-config.js @@ -254,7 +254,69 @@ describe('ClayConfig', function() { }); }); + describe('.destroy().', function() { + it('Destroys all the items on the page and in the items array', function() { + var clayConfig = fixtures.clayConfig(['input', 'text', 'input'], false); + + clayConfig.build(); + clayConfig.destroy(); + + // this should throw because the config has not been built yet + assert.throws(clayConfig.getAllItems); + + // there should be no DOM inside the container + assert.strictEqual(clayConfig.$rootContainer[0].children.length, 0); + }); + + it('dispatches the BEFORE_DESTROY event at the right time', function(done) { + var clayConfig = fixtures.clayConfig(['input', 'text', 'input'], false); + + clayConfig.on(clayConfig.EVENTS.BEFORE_DESTROY, function() { + assert.strictEqual(clayConfig.getAllItems().length, 3); + done(); + }); + + clayConfig.build(); + clayConfig.destroy(); + + assert.strictEqual(clayConfig.getAllItems().length, 0); + }); + + it('dispatches the AFTER_DESTROY event at the right time', function(done) { + var clayConfig = fixtures.clayConfig(['input', 'text', 'input'], false); + + clayConfig.on(clayConfig.EVENTS.AFTER_DESTROY, function() { + + // this should throw because the config has not been built yet + assert.throws(clayConfig.getAllItems); + done(); + }); + + clayConfig.build(); + assert.strictEqual(clayConfig.getAllItems().length, 3); + clayConfig.destroy(); + }); + }); + describe('.build()', function() { + it('Destroys the config if called a consecutive time', function() { + var clayConfig = fixtures.clayConfig(['input', 'text', 'input'], false); + var destroyHandlerSpy = sinon.spy(); + + clayConfig.on(clayConfig.EVENTS.AFTER_DESTROY, destroyHandlerSpy); + + clayConfig.build(); + assert.strictEqual(clayConfig.getAllItems().length, 3); + + clayConfig.config = fixtures.config(['select']); + clayConfig.build(); + assert.strictEqual(clayConfig.getAllItems().length, 1); + assert(destroyHandlerSpy.calledOnce); + + clayConfig.build(); + assert(destroyHandlerSpy.calledTwice); + }); + it('dispatches the BEFORE_BUILD event at the right time', function(done) { var clayConfig = fixtures.clayConfig(['input', 'text', 'input'], false); clayConfig.on(clayConfig.EVENTS.BEFORE_BUILD, function() {