diff --git a/README.md b/README.md index 2abc5e6..25133ff 100755 --- a/README.md +++ b/README.md @@ -479,6 +479,49 @@ A list of options where a user may choose more than one option to submit. --- +### Range Slider + +**Manipulator:** [`slider`](#slider) + +The range slider is used to allow users to select numbers between two values. + +**NOTE** If you set the `step` property to anything less than `1`, +it will multiply the final value sent to the watch by 10 to the power of the number of decimal places in the value of `step`. +Eg: If you set the `step` to `0.25` and the slider has value of `34.5`, the watch will receive `3450`. +The reason why this is necessary, is because you can not send floats to the watch via `Pebble.sendAppMessage()`. +This allows your users to still be able to input values that have decimals, +you must just remember to divide the received value on the watch accordingly. + +##### Properties + +| Property | Type | Description | +|----------|------|-------------| +| type | string | Set to `slider`. | +| defaultValue | number | The value of the slider. | +| label | string | The label that should appear next to this item. | +| min | number | The minimum allowed value of the slider. Defaults to `100` | +| max | number | The maximum allowed value of the slider. Defaults to `0` | +| step | number | The multiple of the values allowed to be set on the slider. The slider will snap to these values. This value also determines the precision used when the value is sent to the watch. Defaults to 1 | +| attributes | object | An object containing HTML attributes to set on the input field. | +| description | string | Optional sub-text to include below the component | + +##### Example + +```javascript +{ + "type": "slider", + "appKey": "slider", + "defaultValue": 15, + "label": "Slider", + "description": "This is the description for the slider", + "min": 10, + "max": 20, + "step": 0.25 +}, +``` + +--- + ### Submit **Manipulator:** [`button`](#button) @@ -506,7 +549,6 @@ The submit button for the page. You **MUST** include this component somewhere in ### Coming Soon -- Range Slider - Tabs - Footer - Dynamic + draggable list @@ -561,7 +603,7 @@ Eg: If you run the `.show()` manipulator on an item that is already visible, the | Method | Returns | Event Fired | Description | |--------|---------|-------------| ------------| | `.set( [boolean\|int] value)` | `ClayItem` | `change` | Check/uncheck the state of this item. | -| `.get()` | `boolean` | | `true` if checked, `false` if not. **NOTE** this will be converted to a `1` or `0` when sent to the watch. See [`ClayConfig.getSettings()`](#methods) | +| `.get()` | `boolean` | | `true` if checked, `false` if not. **NOTE** this will be converted to a `1` or `0` when sent to the watch. See [`Clay.getSettings()`](#methods) | | `.disable()` | `ClayItem` | `disabled` | Prevents this item from being edited by the user. | | `.enable()` | `ClayItem` | `enabled` | Allows this item to be edited by the user. | | `.hide()` | `ClayItem` | `hide` | Hides the item | @@ -594,12 +636,23 @@ Eg: If you run the `.show()` manipulator on an item that is already visible, the | Method | Returns | Event Fired | Description | |--------|---------|-------------| ------------| | `.set( [array] value)` | `ClayItem` | `change` | Checks the checkboxes that corresponds to the provided list of values. | -| `.get()` | `Array.` | | Gets an array of strings representing the list of the values of the checked items. **NOTE:** each item in the array will be separated by a zero when sent to the watch. See [`ClayConfig.getSettings()`](#methods) | +| `.get()` | `Array.` | | Gets an array of strings representing the list of the values of the checked items. **NOTE:** each item in the array will be separated by a zero when sent to the watch. See [`Clay.getSettings()`](#methods) | | `.disable()` | `ClayItem` | `disabled` | Prevents this item from being edited by the user. | | `.enable()` | `ClayItem` | `enabled` | Allows this item to be edited by the user. | | `.hide()` | `ClayItem` | `hide` | Hides the item | | `.show()` | `ClayItem` | `show` | Shows the item | +#### slider + +| Method | Returns | Event Fired | Description | +|--------|---------|-------------| ------------| +| `.set( [number] value)` | `ClayItem` | `change` | Sets the value of this item. | +| `.get()` | `number` | | Gets the value of this item. | +| `.disable()` | `ClayItem` | `disabled` | Prevents this item from being edited by the user. | +| `.enable()` | `ClayItem` | `enabled` | Allows this item to be edited by the user. | +| `.hide()` | `ClayItem` | `hide` | Hides the item | +| `.show()` | `ClayItem` | `show` | Shows the item | + # Extending Clay Clay is built to allow developers to add their own basic interactivity to the config page. This can be done in a number of ways: @@ -771,7 +824,7 @@ This is the main way of talking to your generated config page. An instance of th | `.getItemByAppKey( [string] appKey )` | `ConfigItem\|undefined` - a single `ConfigItem` that has the provided `appKey`, otherwise `undefined`. | | `.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`. | -| `.getSettings()` | `Object` - an object representing all items with an `appKey` where the key is the `appKey` and the value is the result of running `.get()` on the Clay item. | +| `.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` | | `.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` | diff --git a/dev/config.js b/dev/config.js index b19cc2c..b0019c9 100644 --- a/dev/config.js +++ b/dev/config.js @@ -31,6 +31,16 @@ module.exports = [ "placeholder": "Placeholder set with attributes" } }, + { + "type": "slider", + "appKey": "slider", + "defaultValue": 15, + "label": "Slider", + "description": "This is the description for the slider", + "min": 10, + "max": 20, + "step": 0.25 + }, { "type": "toggle", "appKey": "cool_stuff", diff --git a/index.js b/index.js index d99256a..d1f9e98 100755 --- a/index.js +++ b/index.js @@ -3,7 +3,6 @@ var configPageHtml = require('./tmp/config-page.html'); var toSource = require('tosource'); var standardComponents = require('./src/scripts/components'); -var utils = require('./src/scripts/lib/utils'); var deepcopy = require('deepcopy/build/deepcopy.min'); /** @@ -168,9 +167,19 @@ Clay.prototype.getSettings = function(response, convert) { throw new Error('The provided response was not valid JSON'); } - localStorage.setItem('clay-settings', JSON.stringify(settings)); + // flatten the settings for localStorage + var settingsStorage = {}; + Object.keys(settings).forEach(function(key) { + if (typeof settings[key] === 'object' && settings[key]) { + settingsStorage[key] = settings[key].value; + } else { + settingsStorage[key] = settings[key]; + } + }); - return convert === false ? settings : utils.prepareSettingsForAppMessage(settings); + localStorage.setItem('clay-settings', JSON.stringify(settingsStorage)); + + return convert === false ? settings : Clay.prepareSettingsForAppMessage(settings); }; /** @@ -229,4 +238,88 @@ Clay.encodeDataUri = function(input, prefix) { return prefix + encodeURIComponent(out.join('')); }; +/** + * Converts the val into a type compatible with Pebble.sendAppMessage(). + * - Strings will be returned without modification + * - Numbers will be returned without modification + * - Booleans will be converted to a 0 or 1 + * - Arrays that contain strings will be split with a zero. + * eg: ['one', 'two'] becomes ['one', 0, 'two', 0] + * - Arrays that contain numbers will be returned without modification + * eg: [1, 2] becomes [1, 2] + * - Arrays that contain booleans will be converted to a 0 or 1 + * eg: [true, false] becomes [1, 0] + * - Arrays must be single dimensional + * - Objects that have a "value" property will apply the above rules to the type of + * value. If the value is a number or an array of numbers and the optional + * property: "precision" is provided, then the number will be multipled by 10 to + * the power of precision (value * 10 ^ precision) and then floored. + * Eg: 1.4567 with a precision set to 3 will become 1456 + * @param {number|string|boolean|Array|Object} val + * @param {number|string|boolean|Array} val.value + * @param {number} [val.precision=0] + * @returns {number|string|Array} + */ +Clay.prepareForAppMessage = function(val) { + + /** + * moves the decimal place of a number by precision then drop any remaining decimal + * places. + * @param {number} number + * @param {number} precision - number of decimal places to move + * @returns {number} + * @private + */ + function _normalizeToPrecision(number, precision) { + return Math.floor(number * Math.pow(10, precision || 0)); + } + + var result; + + if (Array.isArray(val)) { + result = []; + val.forEach(function(item) { + var itemConverted = Clay.prepareForAppMessage(item); + result.push(itemConverted); + if (typeof itemConverted === 'string') { + result.push(0); + } + }); + } else if (typeof val === 'object' && val) { + if (typeof val.value === 'number') { + result = _normalizeToPrecision(val.value, val.precision); + } else if (Array.isArray(val.value)) { + result = val.value.map(function(item) { + if (typeof item === 'number') { + return _normalizeToPrecision(item, val.precision); + } + return item; + }); + } else { + result = Clay.prepareForAppMessage(val.value); + } + } else if (typeof val === 'boolean') { + result = val ? 1 : 0; + } else { + result = val; + } + + return result; +}; + +/** + * Converts a Clay settings dict into one that is compatible with + * Pebble.sendAppMessage(); + * @see {prepareForAppMessage} + * @param {Object} settings + * @returns {{}} + */ +Clay.prepareSettingsForAppMessage = function(settings) { + var result = {}; + Object.keys(settings).forEach(function(key) { + result[key] = Clay.prepareForAppMessage(settings[key]); + }); + return result; +}; + module.exports = Clay; diff --git a/src/images/example.png b/src/images/example.png index 6d2ebc8..f7246b9 100644 Binary files a/src/images/example.png and b/src/images/example.png differ diff --git a/src/scripts/components/index.js b/src/scripts/components/index.js index 251aba8..18f8d00 100644 --- a/src/scripts/components/index.js +++ b/src/scripts/components/index.js @@ -11,5 +11,6 @@ module.exports = { toggle: require('./toggle'), radiogroup: require('./radiogroup'), checkboxgroup: require('./checkboxgroup'), - button: require('./button') + button: require('./button'), + slider: require('./slider') }; diff --git a/src/scripts/components/slider.js b/src/scripts/components/slider.js new file mode 100644 index 0000000..b80d6ab --- /dev/null +++ b/src/scripts/components/slider.js @@ -0,0 +1,50 @@ +'use strict'; + +module.exports = { + name: 'slider', + template: require('../../templates/components/slider.tpl'), + style: require('../../styles/clay/components/slider.scss'), + manipulator: 'slider', + defaults: { + label: '', + description: '', + min: 0, + max: 100, + step: 1, + attributes: {} + }, + initialize: function() { + var self = this; + + var $value = self.$element.select('.value'); + var $valuePad = self.$element.select('.value-pad'); + var $slider = self.$manipulatorTarget; + + /** + * Sets the value display + * @return {void} + */ + function setValueDisplay() { + var value = self.get(); + $value.set('value', value); + $valuePad.set('innerHTML', value); + } + + var step = $slider.get('step'); + step = step.toString(10).split('.')[1]; + self.precision = step ? step.length : 0; + + self.on('change', setValueDisplay); + $slider.on('|input', setValueDisplay); + setValueDisplay(); + + $value.on('|input', function() { + $valuePad.set('innerHTML', this.get('value')); + }); + + $value.on('|change', function() { + self.set(this.get('value')); + setValueDisplay(); + }); + } +}; diff --git a/src/scripts/config-page.js b/src/scripts/config-page.js index 1f5b1c4..af4f3f5 100755 --- a/src/scripts/config-page.js +++ b/src/scripts/config-page.js @@ -28,7 +28,7 @@ var clayConfig = new ClayConfig(settings, config, $mainForm, clayMeta); $mainForm.on('submit', function() { // Set the return URL depending on the runtime environment location.href = returnTo + - encodeURIComponent(JSON.stringify(clayConfig.getSettings())); + encodeURIComponent(JSON.stringify(clayConfig.serialize())); }); // Run the custom function in the context of the ClayConfig diff --git a/src/scripts/lib/clay-config.js b/src/scripts/lib/clay-config.js index 750e700..e809876 100644 --- a/src/scripts/lib/clay-config.js +++ b/src/scripts/lib/clay-config.js @@ -155,10 +155,16 @@ function ClayConfig(settings, config, $rootContainer, meta) { /** * @returns {Object} */ - self.getSettings = function() { - _checkBuilt('getSettings'); + self.serialize = function() { + _checkBuilt('serialize'); _.eachObj(_itemsByAppKey, function(appKey, item) { - _settings[appKey] = item.get(); + _settings[appKey] = { + value: item.get() + }; + + if (item.precision) { + _settings[appKey].precision = item.precision; + } }); return _settings; }; diff --git a/src/scripts/lib/manipulators.js b/src/scripts/lib/manipulators.js index 84bc11e..25fd76b 100755 --- a/src/scripts/lib/manipulators.js +++ b/src/scripts/lib/manipulators.js @@ -85,6 +85,21 @@ module.exports = { hide: hide, show: show }, + slider: { + get: function() { + return parseFloat(this.$manipulatorTarget.get('value')); + }, + set: function(value) { + var initVal = this.get(); + this.$manipulatorTarget.set('value', value); + if (this.get() === initVal) { return this; } + return this.trigger('change'); + }, + disable: disable, + enable: enable, + hide: hide, + show: show + }, checked: { get: function() { return this.$manipulatorTarget.get('checked'); diff --git a/src/scripts/lib/utils.js b/src/scripts/lib/utils.js index 156a3c4..02079f3 100644 --- a/src/scripts/lib/utils.js +++ b/src/scripts/lib/utils.js @@ -17,54 +17,3 @@ module.exports.updateProperties = function(obj, descriptor) { Object.defineProperty(obj, prop, descriptor); }); }; - -/** - * Converts the val into a type compatible with Pebble.sendAppMessage(). - * - Strings will be returned without modification - * - Numbers will be returned without modification - * - Booleans will be converted to a 0 or 1 - * - Arrays that contain strings will be split with a zero. - * eg: ['one', 'two'] becomes ['one', 0, 'two', 0] - * - Arrays that contain numbers will be returned without modification - * eg: [1, 2] becomes [1, 2] - * - Arrays that contain booleans will be converted to a 0 or 1 - * eg: [true, false] becomes [1, 0] - * - Arrays must be single dimensional - * @param {number|string|boolean|Array} val - * @returns {number|string|Array} - */ -module.exports.prepareForAppMessage = function(val) { - var result; - - if (typeof val === 'boolean') { - result = val ? 1 : 0; - } else if (Array.isArray(val)) { - result = []; - val.forEach(function(item) { - var itemConverted = module.exports.prepareForAppMessage(item); - result.push(itemConverted); - if (typeof itemConverted === 'string') { - result.push(0); - } - }); - } else { - result = val; - } - - return result; -}; - -/** - * Converts a Clay settings dict into one that is compatible with - * Pebble.sendAppMessage(); - * @see {prepareForAppMessage} - * @param {Object} settings - * @returns {{}} - */ -module.exports.prepareSettingsForAppMessage = function(settings) { - var result = {}; - Object.keys(settings).forEach(function(key) { - result[key] = module.exports.prepareForAppMessage(settings[key]); - }); - return result; -}; diff --git a/src/styles/clay/_base.scss b/src/styles/clay/_base.scss index 1e5074a..9e76657 100644 --- a/src/styles/clay/_base.scss +++ b/src/styles/clay/_base.scss @@ -67,6 +67,12 @@ h6 { @include font-size(0.8); } +input { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + label { display: flex; justify-content: space-between; diff --git a/src/styles/clay/components/slider.scss b/src/styles/clay/components/slider.scss new file mode 100644 index 0000000..3ca800b --- /dev/null +++ b/src/styles/clay/components/slider.scss @@ -0,0 +1,127 @@ +@import "clay"; + +.component-slider { + + .section & { + padding: 0; + } + + label { + display: block; + } + + .label-container { + display: flex; + align-items: center; + width: 100%; + padding-bottom: $item-spacing-v; + } + + .label { + flex: 1; + min-width: 1rem; + display: block; + padding-right: $item-spacing-h; + } + + .value-wrap { + display: block; + position: relative; + } + + .value, + .value-pad { + display: block; + background: $color-gray-2; + border-radius: $border-radius; + padding: $item-spacing-v / 2 $item-spacing-h / 2; + border: none; + vertical-align: baseline; + color: $color-white; + text-align: right; + margin: 0; + min-width: 1rem; + } + + .value-pad { + visibility: hidden; + + &:before { + content: ' '; + display: inline-block; + } + } + + .value { + max-width: 100%; + position: absolute; + left:0; + top:0 + } + + .input-wrap { + padding: 0 $item-spacing-h $item-spacing-v; + } + + .input { + $track-height: rem(3px); + + display: block; + position: relative; + min-width: 100%; + height: $base-height; + overflow: hidden; + margin-left: 0; + + &:before { + content: ''; + display: block; + position: absolute; + height: $track-height; + background: $color-gray-6; + width: 100%; + top: $base-height / 2 - $track-height / 2; + } + + .slider { + display: block; + width: 100%; + appearance: none; + position: relative; + height: $base-height; + margin: 0; + background-color: transparent; + + &:focus { + outline: none; + } + + &::-webkit-slider-runnable-track { + border: none; + height: $base-height; + width: 100%; + background-color: transparent; + } + + &::-webkit-slider-thumb { + appearance: none; + position: relative; + height: $base-height; + width: $base-height; + background-color: $color-orange; + border-radius: 50%; + + &:before { + content: ""; + position: absolute; + left: -1000px; + top: $base-height / 2 - $track-height / 2; + height: $track-height; + width: 1001px; + background: $color-orange; + } + } + } + } + +} diff --git a/src/templates/components/slider.tpl b/src/templates/components/slider.tpl new file mode 100644 index 0000000..f5a309f --- /dev/null +++ b/src/templates/components/slider.tpl @@ -0,0 +1,25 @@ +
+ + {{if description}} +
{{{description}}}
+ {{/if}} +
diff --git a/test/spec/components/slider.js b/test/spec/components/slider.js new file mode 100644 index 0000000..9026f5c --- /dev/null +++ b/test/spec/components/slider.js @@ -0,0 +1,68 @@ +'use strict'; + +var assert = require('chai').assert; +var fixture = require('../../fixture'); + +describe('component - slider', function() { + it('sets the value display to the correct value on set', function() { + var clayConfig = fixture.clayConfig(['slider']); + var sliderItem = clayConfig.getItemsByType('slider')[0]; + var $valueDisplay = sliderItem.$element.select('.value'); + var $valueDisplayPad = sliderItem.$element.select('.value-pad'); + + assert.strictEqual($valueDisplay.get('value'), '50'); + assert.strictEqual($valueDisplayPad.get('innerHTML'), '50'); + sliderItem.set(75); + assert.strictEqual($valueDisplay.get('value'), '75'); + assert.strictEqual($valueDisplayPad.get('innerHTML'), '75'); + }); + + it('sets the value display to the correct value on slider change', function() { + var clayConfig = fixture.clayConfig(['slider']); + var sliderItem = clayConfig.getItemsByType('slider')[0]; + var $valueDisplay = sliderItem.$element.select('.value'); + var $valueDisplayPad = sliderItem.$element.select('.value-pad'); + var $slider = sliderItem.$element.select('.slider'); + + assert.strictEqual($valueDisplay.get('value'), '50'); + assert.strictEqual($valueDisplayPad.get('innerHTML'), '50'); + $slider.set('value', 75).trigger('change'); + assert.strictEqual($valueDisplay.get('value'), '75'); + assert.strictEqual($valueDisplayPad.get('innerHTML'), '75'); + }); + + it('sets the value-pad to the same value as what the user is typing', function() { + var clayConfig = fixture.clayConfig(['slider']); + var sliderItem = clayConfig.getItemsByType('slider')[0]; + var $valueDisplay = sliderItem.$element.select('.value'); + var $valueDisplayPad = sliderItem.$element.select('.value-pad'); + + assert.strictEqual($valueDisplay.get('value'), '50'); + assert.strictEqual($valueDisplayPad.get('innerHTML'), '50'); + $valueDisplay.set('value', 75).trigger('input'); + assert.strictEqual($valueDisplay.get('value'), '75'); + assert.strictEqual($valueDisplayPad.get('innerHTML'), '75'); + }); + + it('sets the slider to the correct value on user input', function() { + var clayConfig = fixture.clayConfig(['slider']); + var sliderItem = clayConfig.getItemsByType('slider')[0]; + var $valueDisplay = sliderItem.$element.select('.value'); + var $slider = sliderItem.$element.select('.slider'); + + assert.strictEqual($valueDisplay.get('value'), '50'); + assert.strictEqual($slider.get('value'), '50'); + $valueDisplay.set('value', 75).trigger('change'); + assert.strictEqual($slider.get('value'), '75'); + }); + + it('sets the precision correctly', function() { + var clayConfig = fixture.clayConfig([{ + type: 'slider', + step: 0.25 + }]); + var sliderItem = clayConfig.getItemsByType('slider')[0]; + + assert.strictEqual(sliderItem.precision, 2); + }); +}); diff --git a/test/spec/index.js b/test/spec/index.js index b7815a7..da40a69 100644 --- a/test/spec/index.js +++ b/test/spec/index.js @@ -230,13 +230,21 @@ describe('Clay', function() { }); }); - describe('.getSettings()', function() { + describe('.getSettings', function() { it('stores the response to localStorage and returns the decoded data', function() { var clay = fixture.clay([]); - var result = clay.getSettings('%7B%22appKey%22%3A%22value%22%7D'); - assert.equal(localStorage.getItem('clay-settings'), '{"appKey":"value"}'); - assert.deepEqual(result, {appKey: 'value'}); + var settings = encodeURIComponent(JSON.stringify({ + key1: 'value1', + key2: {value: 'value2'} + })); + var expected = { + key1: 'value1', + key2: 'value2' + }; + var result = clay.getSettings(settings); + assert.equal(localStorage.getItem('clay-settings'), JSON.stringify(expected)); + assert.deepEqual(result, expected); }); it('does not store the response if it is invalid JSON and logs an error', @@ -259,7 +267,15 @@ describe('Clay', function() { test4: ['cb-1', 'cb-3'], test5: 12345, test6: [1, 2, 3, 4], - test7: [true, false, true] + test7: [true, false, true], + test8: { + precision: 2, + value: 12.34 + }, + test9: { + precision: 1, + value: [1, 2, 3, 4] + } })); var expected = { test1: 0, @@ -268,7 +284,9 @@ describe('Clay', function() { test4: ['cb-1', 0, 'cb-3', 0], test5: 12345, test6: [1, 2, 3, 4], - test7: [1, 0, 1] + test7: [1, 0, 1], + test8: 1234, + test9: [10, 20, 30, 40] }; assert.deepEqual(clay.getSettings(response), expected); @@ -284,7 +302,11 @@ describe('Clay', function() { test4: ['cb-1', 'cb-3'], test5: 12345, test6: [1, 2, 3, 4], - test7: [true, false, true] + test7: [true, false, true], + test8: { + precision: 2, + value: 12.34 + } }; var response = encodeURIComponent(JSON.stringify(settings)); @@ -381,4 +403,98 @@ describe('Clay', function() { }); }); + + describe('.prepareForAppMessage', function() { + it('converts an array correctly when array contains numbers', function() { + assert.deepEqual(Clay.prepareForAppMessage([1, 2, 3]), [1, 2, 3]); + assert.deepEqual(Clay.prepareForAppMessage( + {value: [1.5, 2.34, 3], precision: 2}), + [150, 234, 300] + ); + }); + + it('converts an array correctly when array contains booleans', function() { + assert.deepEqual(Clay.prepareForAppMessage([true, false, true]), [1, 0, 1]); + assert.deepEqual( + Clay.prepareForAppMessage([{value: true}, false, true]), + [1, 0, 1]); + }); + + it('converts booleans to ints', function() { + assert.strictEqual(Clay.prepareForAppMessage(false), 0); + assert.strictEqual(Clay.prepareForAppMessage(true), 1); + assert.strictEqual(Clay.prepareForAppMessage({value: false}), 0); + assert.strictEqual(Clay.prepareForAppMessage({value: true}), 1); + }); + + it('leaves strings alone', function() { + assert.strictEqual(Clay.prepareForAppMessage('test'), 'test'); + assert.strictEqual(Clay.prepareForAppMessage({value: 'test'}), 'test'); + }); + + it('Multiples the number by the precision', function() { + assert.strictEqual(Clay.prepareForAppMessage(123), 123); + assert.strictEqual(Clay.prepareForAppMessage({ + value: 1.23, + precision: 3 + }), 1230); + assert.strictEqual(Clay.prepareForAppMessage({ + value: 123, + precision: 2 + }), 12300); + assert.strictEqual(Clay.prepareForAppMessage({ + value: 1.23456, + precision: 4 + }), 12345); + }); + + it('ignores precision for anything but numbers', function() { + assert.strictEqual(Clay.prepareForAppMessage({ + value: 'not a number', + precision: 3 + }), 'not a number'); + + assert.deepEqual(Clay.prepareForAppMessage({ + value: ['not', 'a', 'number'], + precision: 3 + }), ['not', 'a', 'number']); + }); + + it('treats an undefined precision as 0', function() { + assert.strictEqual(Clay.prepareForAppMessage({ + value: 12.45, + precision: undefined + }), 12); + }); + }); + + describe('.prepareSettingsForAppMessage', function() { + it('converts the settings correctly', function() { + var settings = { + test1: false, + test2: 'val-2', + test3: true, + test4: ['cb-1', 'cb-3'], + test5: 12345, + test6: [1, 2, 3, 4], + test7: [true, false, true], + test8: { + precision: 2, + value: 12.34 + } + }; + var expected = { + test1: 0, + test2: 'val-2', + test3: 1, + test4: ['cb-1', 0, 'cb-3', 0], + test5: 12345, + test6: [1, 2, 3, 4], + test7: [1, 0, 1], + test8: 1234 + }; + + assert.deepEqual(Clay.prepareSettingsForAppMessage(settings), expected); + }); + }); }); diff --git a/test/spec/lib/clay-config.js b/test/spec/lib/clay-config.js index 887186a..dba8b34 100644 --- a/test/spec/lib/clay-config.js +++ b/test/spec/lib/clay-config.js @@ -15,7 +15,7 @@ describe('ClayConfig', function() { 'getItemByAppKey', 'getItemById', 'getItemsByType', - 'getSettings', + 'serialize', 'registerComponent', 'build', 'on', @@ -39,7 +39,7 @@ describe('ClayConfig', function() { 'getItemByAppKey', 'getItemById', 'getItemsByType', - 'getSettings' + 'serialize' ].forEach(function(method) { it('.' + method + '()', function() { var clayConfig = fixtures.clayConfig(['input', 'text'], false); @@ -99,7 +99,7 @@ describe('ClayConfig', function() { }); }); - describe('.getSettings()', function() { + describe('.serialize()', function() { it('returns the correct settings', function() { var config = [ {type: 'input', appKey: 'test1', defaultValue: 'default val'}, @@ -112,7 +112,8 @@ describe('ClayConfig', function() { {label: 'label-1', value: 'cb-1'}, {label: 'label-2', value: 'cb-2'}, {label: 'label-2', value: 'cb-3'} - ]} + ]}, + {type: 'slider', appKey: 'test5', step: 0.05, defaultValue: 12.5} ]; var settings = { test2: 'val-2' @@ -120,29 +121,42 @@ describe('ClayConfig', function() { var clayConfig = fixtures.clayConfig(config, true, true, settings); - assert.deepEqual(clayConfig.getSettings(), { - test1: 'default val', - test2: 'val-2', - test3: false, - test4: [] + assert.deepEqual(clayConfig.serialize(), { + test1: {value: 'default val'}, + test2: {value: 'val-2'}, + test3: {value: false}, + test4: {value: []}, + test5: {value: 12.5, precision: 2} }); clayConfig.getItemByAppKey('test1').set('val-1'); clayConfig.getItemByAppKey('test3').set(true); clayConfig.getItemByAppKey('test4').set(['cb-1', 'cb-3']); - assert.deepEqual(clayConfig.getSettings(), { + assert.deepEqual(clayConfig.serialize(), { + test1: {value: 'val-1'}, + test2: {value: 'val-2'}, + test3: {value: true}, + test4: {value: ['cb-1', 'cb-3']}, + test5: {value: 12.5, precision: 2} + }); + + // make sure the result of serialize() can actually be fed back in to + // a new instance of ClayConfig + var clay = fixtures.clay(config); + var response = encodeURIComponent(JSON.stringify(clayConfig.serialize())); + clay.getSettings(response); + var loadedSettings = JSON.parse(localStorage.getItem('clay-settings')); + + assert.deepEqual(loadedSettings, { test1: 'val-1', test2: 'val-2', test3: true, - test4: ['cb-1', 'cb-3'] + test4: ['cb-1', 'cb-3'], + test5: 12.5 }); - - // make sure the result of getSettings() can actually be fed back in to - // a new instance of ClayConfig assert.doesNotThrow(function() { - settings = clayConfig.getSettings(); - fixtures.clayConfig(config, true, true, settings); + fixtures.clayConfig(config, true, true, loadedSettings); }); }); }); diff --git a/test/spec/lib/manipulators.js b/test/spec/lib/manipulators.js index 1dc7828..af886c0 100644 --- a/test/spec/lib/manipulators.js +++ b/test/spec/lib/manipulators.js @@ -143,43 +143,67 @@ describe('manipulators', function() { } describe('html', function() { - testSetGet('text', 'test123'); - testSetGet('button', 'some HTML'); - testShow('text'); - testHide('text'); + var type = 'text'; + testSetGet(type, 'test123'); + testSetGet(type, 'some HTML'); + testShow(type); + testHide(type); }); describe('button', function() { - testSetGet('button', 'test123'); - testSetGet('button', 'some HTML'); - testDisable('button'); - testEnable('button'); - testShow('button'); - testHide('button'); + var type = 'button'; + testSetGet(type, 'test123'); + testSetGet(type, 'some HTML'); + testDisable(type); + testEnable(type); + testShow(type); + testHide(type); }); describe('val', function() { - testSetGet('input', 'test321'); - testSetGet('input', 1234, '1234'); - testDisable('input'); - testEnable('input'); - testShow('text'); - testHide('text'); + var type = 'input'; + testSetGet(type, 'test321'); + testSetGet(type, 1234, '1234'); + testDisable(type); + testEnable(type); + testShow(type); + testHide(type); + }); + + describe('slider', function() { + var type = { + type: 'slider', + min: 0, + max: 100, + step: 0.1 + }; + + testSetGet(type, '12', 12); + testSetGet(type, 12); + testSetGet(type, '12.3', 12.3); + testSetGet(type, 12.3); + testSetGet(type, 12.34, 12.3); + testSetGet(type, '12.34', 12.3); + testDisable(type); + testEnable(type); + testShow(type); + testHide(type); }); describe('checked', function() { - testSetGet({type: 'toggle', defaultValue: false}, 1, true); - testSetGet({type: 'toggle', defaultValue: false}, true); - testSetGet({type: 'toggle', defaultValue: true}, 0, false); - testSetGet({type: 'toggle', defaultValue: true}, false); - testDisable('toggle'); - testEnable('toggle'); - testShow('toggle'); - testHide('toggle'); + var type = 'toggle'; + testSetGet({type: type, defaultValue: false}, 1, true); + testSetGet({type: type, defaultValue: false}, true); + testSetGet({type: type, defaultValue: true}, 0, false); + testSetGet({type: type, defaultValue: true}, false); + testDisable(type); + testEnable(type); + testShow(type); + testHide(type); }); describe('radiogroup', function() { - var item = { + var type = { type: 'radiogroup', clayId: 1, options: [ @@ -188,17 +212,17 @@ describe('manipulators', function() { { label: '3', value: 'three "quote' } ] }; - testSetGet(item, 'one'); - testSetGet(item, 'two'); - testSetGet(item, 'three "quote'); - testDisable(item); - testEnable(item); - testShow(item); - testHide(item); + testSetGet(type, 'one'); + testSetGet(type, 'two'); + testSetGet(type, 'three "quote'); + testDisable(type); + testEnable(type); + testShow(type); + testHide(type); }); describe('checkboxgroup', function() { - var item = { + var type = { type: 'checkboxgroup', clayId: 1, defaultValue: ['two'], @@ -208,28 +232,29 @@ describe('manipulators', function() { { label: '3', value: 'three "quote' } ] }; - testSetGet(item, ['one', 'two']); - testSetGet(item, ['three "quote']); - testSetGet(item, []); - testSetGet(item, false, []); - testDisable(item); - testEnable(item); - testShow(item); - testHide(item); + testSetGet(type, ['one', 'two']); + testSetGet(type, ['three "quote']); + testSetGet(type, []); + testSetGet(type, false, []); + testDisable(type); + testEnable(type); + testShow(type); + testHide(type); }); describe('color', function() { - testSetGet('color', 'FF0000', 0xff0000); - testSetGet('color', '#FF0000', 0xff0000); - testSetGet('color', '0xFF0000', 0xff0000); - testSetGet('color', '#ff0000', 0xff0000); - testSetGet('color', 0xff0000, 0xff0000); - testSetGet({type: 'color', defaultValue: 0x00ff00}, '', 0x000000); - testSetGet({type: 'color', defaultValue: 0x00ff00}, false, 0x000000); - testSetGet({type: 'color', defaultValue: 0x00ff00}, undefined, 0x000000); - testDisable('color'); - testEnable('color'); - testShow('color'); - testHide('color'); + var type = 'color'; + testSetGet(type, 'FF0000', 0xff0000); + testSetGet(type, '#FF0000', 0xff0000); + testSetGet(type, '0xFF0000', 0xff0000); + testSetGet(type, '#ff0000', 0xff0000); + testSetGet(type, 0xff0000, 0xff0000); + testSetGet({type: type, defaultValue: 0x00ff00}, '', 0x000000); + testSetGet({type: type, defaultValue: 0x00ff00}, false, 0x000000); + testSetGet({type: type, defaultValue: 0x00ff00}, undefined, 0x000000); + testDisable(type); + testEnable(type); + testShow(type); + testHide(type); }); }); diff --git a/test/spec/lib/utils.js b/test/spec/lib/utils.js index 22127e9..4acbcce 100644 --- a/test/spec/lib/utils.js +++ b/test/spec/lib/utils.js @@ -26,60 +26,5 @@ describe('Utils', function() { ); }); }); - - describe('.prepareForAppMessage', function() { - it('converts an array correctly when array contains strings', function() { - assert.deepEqual( - utils.prepareForAppMessage(['one', 'two']), - ['one', 0, 'two', 0] - ); - }); - - it('converts an array correctly when array contains numbers', function() { - assert.deepEqual(utils.prepareForAppMessage([1, 2, 3]), [1, 2, 3]); - }); - - it('converts an array correctly when array contains booleans', function() { - assert.deepEqual(utils.prepareForAppMessage([true, false, true]), [1, 0, 1]); - }); - - it('converts booleans to ints', function() { - assert.strictEqual(utils.prepareForAppMessage(false), 0); - assert.strictEqual(utils.prepareForAppMessage(true), 1); - }); - - it('leaves strings alone', function() { - assert.strictEqual(utils.prepareForAppMessage('test'), 'test'); - }); - - it('leaves numbers alone', function() { - assert.strictEqual(utils.prepareForAppMessage(123), 123); - }); - }); - - describe('.prepareSettingsForAppMessage', function() { - it('converts the settings correctly', function() { - var settings = { - test1: false, - test2: 'val-2', - test3: true, - test4: ['cb-1', 'cb-3'], - test5: 12345, - test6: [1, 2, 3, 4], - test7: [true, false, true] - }; - var expected = { - test1: 0, - test2: 'val-2', - test3: 1, - test4: ['cb-1', 0, 'cb-3', 0], - test5: 12345, - test6: [1, 2, 3, 4], - test7: [1, 0, 1] - }; - - assert.deepEqual(utils.prepareSettingsForAppMessage(settings), expected); - }); - }); });