Merge remote-tracking branch 'origin/master' into #11/description-for-input-components

# Conflicts:
#	dist/clay.js
#	src/styles/clay/_base.scss
#	src/templates/components/select.tpl
#	src/templates/components/toggle.tpl
This commit is contained in:
Keegan
2016-03-02 10:58:17 +11:00
26 changed files with 236 additions and 46 deletions
+6 -2
View File
@@ -613,7 +613,11 @@ This is the main way of talking to your generated config page.
|----------|------|-------------| |----------|------|-------------|
| `.EVENTS.BEFORE_BUILD` | String | Dispatched prior to building the page. | | `.EVENTS.BEFORE_BUILD` | String | Dispatched prior to building the page. |
| `.EVENTS.AFTER_BUILD` | String | Dispatched after building the page. | | `.EVENTS.AFTER_BUILD` | String | Dispatched after building the page. |
| `.config` | Array | Reference to the config passed to the constructer and used for generating 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). |
| `.meta.accountToken` | String | A unique account token that is associated with the Pebble account of the current user. Read more [here](https://developer.pebble.com/docs/js/Pebble/#getAccountToken). |
| `.meta.watchToken` | String | A unique token that can be used to identify a Pebble device. Read more [here](https://developer.pebble.com/docs/js/Pebble/#getWatchToken). |
#### Methods #### Methods
@@ -643,7 +647,7 @@ This is the main way of talking to your generated config page.
| `.appKey` | String | The ID of the item if provided in the config. | | `.appKey` | String | The ID of the item if provided in the config. |
| `.config` | Object | Reference to the config passed to the constructer. | | `.config` | Object | Reference to the config passed to the constructer. |
| `$element` | $Minified | A Minified list representing the root HTML element of the config item. | | `$element` | $Minified | A Minified list representing the root HTML element of the config item. |
| `$manipulatorTarget` | A Minified list representing the HTML element with **data-manipulator-target** set. This is generally pointing to the main `<input>` element and will be used for binding events. | | `$manipulatorTarget` | $Minified | A Minified list representing the HTML element with **data-manipulator-target** set. This is generally pointing to the main `<input>` element and will be used for binding events. |
#### Methods #### Methods
+2 -2
View File
@@ -10,9 +10,9 @@ module.exports = function() {
*/ */
function toggleBackground() { function toggleBackground() {
if (this.get()) { if (this.get()) {
Clay.getItemByAppKey('background').enable(); Clay.getItemByAppKey('colorTest').enable();
} else { } else {
Clay.getItemByAppKey('background').disable(); Clay.getItemByAppKey('colorTest').disable();
} }
} }
+1
View File
@@ -103,6 +103,7 @@ gulp.task('dev-js', ['js', 'sass'], function() {
.transform(stringify(stringifyOptions)) .transform(stringify(stringifyOptions))
.transform('deamdify') .transform('deamdify')
.transform(sassify, sassifyOptions) .transform(sassify, sassifyOptions)
.transform(autoprefixify, autoprefixerOptions)
.bundle() .bundle()
.pipe(source('dev.js')) .pipe(source('dev.js'))
.pipe(gulp.dest('./tmp/')); .pipe(gulp.dest('./tmp/'));
+25 -1
View File
@@ -29,11 +29,17 @@ function Clay(config, customFn, options) {
self.config = config; self.config = config;
self.customFn = customFn || function() {}; self.customFn = customFn || function() {};
self.components = {}; self.components = {};
self.meta = {
activeWatchInfo: null,
accountToken: '',
watchToken: ''
};
// Let Clay handle all the magic // Let Clay handle all the magic
if (options.autoHandleEvents !== false && typeof Pebble !== 'undefined') { if (options.autoHandleEvents !== false && typeof Pebble !== 'undefined') {
Pebble.addEventListener('showConfiguration', function() { Pebble.addEventListener('showConfiguration', function() {
_populateMeta();
Pebble.openURL(self.generateUrl()); Pebble.openURL(self.generateUrl());
}); });
@@ -49,6 +55,23 @@ function Clay(config, customFn, options) {
console.log(JSON.stringify(error)); console.log(JSON.stringify(error));
}); });
}); });
} else if (typeof Pebble !== 'undefined') {
Pebble.addEventListener('ready', function() {
_populateMeta();
});
}
/**
* Populate the meta with data from the Pebble object. Make sure to run this inside
* either the "showConfiguration" or "ready" event handler
* @return {void}
*/
function _populateMeta() {
self.meta = {
activeWatchInfo: Pebble.getActiveWatchInfo && Pebble.getActiveWatchInfo(),
accountToken: Pebble.getAccountToken(),
watchToken: Pebble.getWatchToken()
};
} }
/** /**
@@ -107,7 +130,8 @@ Clay.prototype.generateUrl = function() {
.replace('$$CUSTOM_FN$$', toSource(this.customFn)) .replace('$$CUSTOM_FN$$', toSource(this.customFn))
.replace('$$CONFIG$$', toSource(this.config)) .replace('$$CONFIG$$', toSource(this.config))
.replace('$$SETTINGS$$', toSource(settings)) .replace('$$SETTINGS$$', toSource(settings))
.replace('$$COMPONENTS$$', toSource(this.components)); .replace('$$COMPONENTS$$', toSource(this.components))
.replace('$$META$$', toSource(this.meta));
// if we are in the emulator then we need to proxy the data via a webpage to // if we are in the emulator then we need to proxy the data via a webpage to
// obtain the return_to. // obtain the return_to.
+1
View File
@@ -10,6 +10,7 @@
window.claySettings = $$SETTINGS$$; window.claySettings = $$SETTINGS$$;
window.customFn = $$CUSTOM_FN$$; window.customFn = $$CUSTOM_FN$$;
window.clayComponents = $$COMPONENTS$$; window.clayComponents = $$COMPONENTS$$;
window.clayMeta = $$META$$;
</script> </script>
</head> </head>
<body> <body>
+4 -2
View File
@@ -16,8 +16,10 @@ module.exports = {
var $value = self.$element.select('.value'); var $value = self.$element.select('.value');
self.on('change', function(ev) { self.on('change', function() {
var value = self.$manipulatorTarget.select('option:checked').get('innerHTML'); var selectedIndex = self.$manipulatorTarget.get('selectedIndex');
var $options = self.$manipulatorTarget.select('option');
var value = $options[selectedIndex] && $options[selectedIndex].innerHTML;
$value.set('innerHTML', value); $value.set('innerHTML', value);
}); });
} }
+1
View File
@@ -3,6 +3,7 @@
module.exports = { module.exports = {
name: 'submit', name: 'submit',
template: require('../../templates/components/submit.tpl'), template: require('../../templates/components/submit.tpl'),
style: require('../../styles/clay/components/submit.scss'),
manipulator: 'html', manipulator: 'html',
defaults: { defaults: {
attributes: {} attributes: {}
+2 -1
View File
@@ -11,6 +11,7 @@ var settings = _.extend({}, window.claySettings || {});
var returnTo = window.returnTo || 'pebblejs://close#'; var returnTo = window.returnTo || 'pebblejs://close#';
var customFn = window.customFn || function() {}; var customFn = window.customFn || function() {};
var clayComponents = window.clayComponents || {}; var clayComponents = window.clayComponents || {};
var clayMeta = window.clayMeta || {};
var platform = window.navigator.userAgent.match(/android/i) ? 'android' : 'ios'; var platform = window.navigator.userAgent.match(/android/i) ? 'android' : 'ios';
document.documentElement.classList.add('platform-' + platform); document.documentElement.classList.add('platform-' + platform);
@@ -21,7 +22,7 @@ _.eachObj(clayComponents, function(key, component) {
}); });
var $mainForm = $('#main-form'); var $mainForm = $('#main-form');
var clayConfig = new ClayConfig(settings, config, $mainForm); var clayConfig = new ClayConfig(settings, config, $mainForm, clayMeta);
// add listeners here // add listeners here
$mainForm.on('submit', function() { $mainForm.on('submit', function() {
+4 -2
View File
@@ -26,9 +26,10 @@ var manipulators = require('./manipulators');
* @param {Object} settings - setting that were set from a previous session * @param {Object} settings - setting that were set from a previous session
* @param {Array|Object} config * @param {Array|Object} config
* @param {M} $rootContainer * @param {M} $rootContainer
* @param {Object} meta
* @constructor * @constructor
*/ */
function ClayConfig(settings, config, $rootContainer) { function ClayConfig(settings, config, $rootContainer, meta) {
var self = this; var self = this;
var _settings = _.copyObj(settings); var _settings = _.copyObj(settings);
@@ -95,6 +96,8 @@ function ClayConfig(settings, config, $rootContainer) {
return true; return true;
} }
self.meta = meta;
self.EVENTS = { self.EVENTS = {
/** /**
* Called before framework has initialized. This is when you would attach your * Called before framework has initialized. This is when you would attach your
@@ -183,7 +186,6 @@ function ClayConfig(settings, config, $rootContainer) {
// expose the config to allow developers to update it before the build is run // expose the config to allow developers to update it before the build is run
self.config = config; self.config = config;
} }
/** /**
+12 -5
View File
@@ -83,9 +83,12 @@ label {
} }
} }
.tap-highlight {
@include tap-highlight();
}
.component { .component {
padding-bottom: $item-spacing-v; padding-bottom: $item-spacing-v;
@include tap-highlight();
&.disabled { &.disabled {
opacity: 0.25; opacity: 0.25;
@@ -108,10 +111,10 @@ label {
&:last-child, &:last-child,
&:first-child { &:first-child {
padding-bottom: $item-spacing-v; padding-bottom: $item-spacing-v;
}
&:after {
display: none; &:last-child:after {
} display: none;
} }
&:after { &:after {
@@ -130,6 +133,10 @@ label {
.component-heading:first-child { .component-heading:first-child {
background: $color-gray-3; background: $color-gray-3;
border-radius: $border-radius $border-radius 0 0; border-radius: $border-radius $border-radius 0 0;
&:after {
display: none;
}
} }
} }
@@ -3,10 +3,8 @@
.component-checkbox { .component-checkbox {
@include tap-highlight(rgba(0,0,0,0));
display: block; display: block;
> .label { > .label {
display: block; display: block;
padding-bottom: $item-spacing-v / 2; padding-bottom: $item-spacing-v / 2;
@@ -16,7 +14,6 @@
label { label {
padding: $item-spacing-v / 2 0; padding: $item-spacing-v / 2 0;
@include tap-highlight();
} }
.label { .label {
@@ -39,7 +36,7 @@
flex-shrink: 0; flex-shrink: 0;
} }
input:checked ~ i { input:checked + i {
border-color: $color-orange; border-color: $color-orange;
background: $color-orange; background: $color-orange;
+1
View File
@@ -7,6 +7,7 @@
height: $base-height; height: $base-height;
border-radius: $base-height / 2; border-radius: $base-height / 2;
box-shadow: $box-shadow-small-components; box-shadow: $box-shadow-small-components;
display: block;
} }
.picker-wrap { .picker-wrap {
+1 -4
View File
@@ -3,10 +3,8 @@
.component-radio { .component-radio {
@include tap-highlight(rgba(0,0,0,0));
display: block; display: block;
> .label { > .label {
display: block; display: block;
padding-bottom: $item-spacing-v / 2; padding-bottom: $item-spacing-v / 2;
@@ -16,7 +14,6 @@
label { label {
padding: $item-spacing-v / 2 0; padding: $item-spacing-v / 2 0;
@include tap-highlight();
} }
.label { .label {
@@ -39,7 +36,7 @@
flex-shrink: 0; flex-shrink: 0;
} }
input:checked ~ i { input:checked + i {
border-color: $color-orange; border-color: $color-orange;
&:after { &:after {
+1
View File
@@ -11,6 +11,7 @@
$triangle-size: 0.85rem; $triangle-size: 0.85rem;
position: relative; position: relative;
padding-right: $triangle-size + 0.25rem; padding-right: $triangle-size + 0.25rem;
display: block;
&:after { &:after {
content: ""; content: "";
+3
View File
@@ -0,0 +1,3 @@
.component-submit {
text-align: center;
}
+1 -1
View File
@@ -9,7 +9,7 @@ button,
font-size: 1rem; font-size: 1rem;
line-height: 1; line-height: 1;
border: none; border: none;
display: block; display: inline-block;
color: $color-white; color: $color-white;
min-width: 12rem; min-width: 12rem;
+1 -1
View File
@@ -2,7 +2,7 @@
<span class="label">{{{label}}}</span> <span class="label">{{{label}}}</span>
<div class="checkbox-group"> <div class="checkbox-group">
{{each options}} {{each options}}
<label> <label class="tap-highlight">
<span class="label">{{{this.label}}}</span> <span class="label">{{{this.label}}}</span>
<input <input
type="checkbox" type="checkbox"
+1 -1
View File
@@ -1,4 +1,4 @@
<div class="component component-color"> <div class="component component-color tap-highlight">
<label> <label>
<input <input
data-manipulator-target data-manipulator-target
+1 -1
View File
@@ -1,4 +1,4 @@
<label class="component component-input"> <label class="component component-input tap-highlight">
<span class="label">{{{label}}}</span> <span class="label">{{{label}}}</span>
<span class="input"> <span class="input">
<input <input
+1 -1
View File
@@ -2,7 +2,7 @@
<span class="label">{{{label}}}</span> <span class="label">{{{label}}}</span>
<div class="radio-group"> <div class="radio-group">
{{each options}} {{each options}}
<label> <label class="tap-highlight">
<span class="label">{{{this.label}}}</span> <span class="label">{{{this.label}}}</span>
<input <input
type="radio" type="radio"
+1 -1
View File
@@ -1,4 +1,4 @@
<div class="component component-select"> <div class="component component-select tap-highlight">
<label> <label>
<span class="label">{{{label}}}</span> <span class="label">{{{label}}}</span>
<span class="value"></span> <span class="value"></span>
+1 -1
View File
@@ -1,4 +1,4 @@
<div class="component component-button"> <div class="component component-submit">
<button <button
data-manipulator-target data-manipulator-target
type="submit" type="submit"
+1 -1
View File
@@ -1,5 +1,5 @@
<div class="component component-toggle"> <div class="component component-toggle">
<label> <label class="tap-highlight">
<span class="label">{{{label}}}</span> <span class="label">{{{label}}}</span>
<span class="input"> <span class="input">
<input <input
+25 -1
View File
@@ -10,6 +10,29 @@ var components = require('../src/scripts/components');
var componentRegistry = require('../src/scripts/lib/component-registry'); var componentRegistry = require('../src/scripts/lib/component-registry');
var idCounter = 0; var idCounter = 0;
/**
* @returns {{accountToken: string, watchToken: string, activeWatchInfo: {platform:
* string, model: string, language: string, firmware: {major: number, minor:
* number, patch: number, suffix: string}}}}
*/
module.exports.meta = function() {
return {
accountToken: '0123456789abcdef0123456789abcdef',
watchToken: '0123456789abcdef0123456789abcdef',
activeWatchInfo: {
platform: 'chalk',
model: 'qemu_platform_chalk',
language: 'en_US',
firmware: {
major: 3,
minor: 3,
patch: 2,
suffix: ''
}
}
};
};
/** /**
* @param {string|Object} config * @param {string|Object} config
* @param {boolean} [autoRegister=true] * @param {boolean} [autoRegister=true]
@@ -70,7 +93,8 @@ module.exports.clayConfig = function(types, build, autoRegister, settings) {
var clayConfig = new ClayConfig( var clayConfig = new ClayConfig(
settings || {}, settings || {},
module.exports.config(types, autoRegister), module.exports.config(types, autoRegister),
$(HTML('<div>')) $(HTML('<div>')),
module.exports.meta()
); );
return build === false ? clayConfig : clayConfig.build(); return build === false ? clayConfig : clayConfig.build();
}; };
+129 -13
View File
@@ -5,6 +5,21 @@ var Clay = require('../../index');
var assert = require('chai').assert; var assert = require('chai').assert;
var standardComponents = require('../../src/scripts/components'); var standardComponents = require('../../src/scripts/components');
var sinon = require('sinon'); var sinon = require('sinon');
var toSource = require('tosource');
var accountToken = '0123456789abcdef0123456789abcdef';
var watchToken = '0123456789abcdef0123456789abcdef';
var activeWatchInfo = {
platform: 'chalk',
model: 'qemu_platform_chalk',
language: 'en_US',
firmware: {
major: 3,
minor: 3,
patch: 2,
suffix: ''
}
};
/** /**
* @return {void} * @return {void}
@@ -13,7 +28,10 @@ function stubPebble() {
global.Pebble = { global.Pebble = {
addEventListener: sinon.stub(), addEventListener: sinon.stub(),
openURL: sinon.stub(), openURL: sinon.stub(),
sendAppMessage: sinon.stub() sendAppMessage: sinon.stub(),
getActiveWatchInfo: sinon.stub().returns(activeWatchInfo),
getAccountToken: sinon.stub().returns(accountToken),
getWatchToken: sinon.stub().returns(watchToken)
}; };
} }
@@ -56,6 +74,8 @@ describe('Clay', function() {
it('handles the "showConfiguration" event if autoHandleEvents is not false', it('handles the "showConfiguration" event if autoHandleEvents is not false',
function() { function() {
this.timeout(10000); // 10 seconds
stubPebble(); stubPebble();
var clay = fixture.clay([]); var clay = fixture.clay([]);
@@ -104,7 +124,14 @@ describe('Clay', function() {
'if autoHandleEvents is false', function() { 'if autoHandleEvents is false', function() {
stubPebble(); stubPebble();
fixture.clay([], null, { autoHandleEvents: false }); fixture.clay([], null, { autoHandleEvents: false });
assert.strictEqual(Pebble.addEventListener.called, false); assert.strictEqual(
Pebble.addEventListener.withArgs('webviewclosed').called,
false
);
assert.strictEqual(
Pebble.addEventListener.withArgs('showConfiguration').called,
false
);
}); });
}); });
@@ -123,23 +150,66 @@ describe('Clay', function() {
describe('.generateUrl()', function() { describe('.generateUrl()', function() {
// @todo this test becomes redundant with the work in 94428b1 /**
it('Replaces $$RETURN_TO$$ if in not in the emulator', function() { * Decode the generated data URI into just the HTML portion
var clay = fixture.clay([]); * @param {string} url
stubPebble(); * @returns {string}
Pebble.platform = 'ios'; */
var decodedUrl = function decodeUrl(url) {
atob(decodeURIComponent(clay.generateUrl().replace(/^.*?,/, ''))); return atob(decodeURIComponent(url.replace(/^.*?[#,]/, '')));
assert.match(decodedUrl, /pebblejs:\/\/close#/); }
describe('string substitutions', function() {
var customFn = function() { this.getAllItems(); };
var config = fixture.config(['input', 'color']);
var settings = { appKey: 'value' };
var clay;
var html;
before(function() {
stubPebble();
clay = fixture.clay(config, customFn);
Pebble.addEventListener.withArgs('showConfiguration').callArg(1);
localStorage.setItem('clay-settings', JSON.stringify(settings));
html = decodeUrl(clay.generateUrl());
});
it('Substitutes $$RETURN_TO$$ with the pebblejs://close#', function() {
assert.notInclude(html, '$$RETURN_TO$$');
assert.include(html, 'window.returnTo="pebblejs://close#"');
});
it('Substitutes $$CUSTOM_FN$$ with the customFn', function() {
assert.notInclude(html, '$$CUSTOM_FN$$');
assert.include(html, 'window.customFn=' + toSource(customFn));
});
it('Substitutes $$CONFIG$$ with the config', function() {
assert.notInclude(html, '$$CONFIG$$');
assert.include(html, 'window.clayConfig=' + toSource(config));
});
it('Substitutes $$SETTINGS$$ with the config', function() {
assert.notInclude(html, '$$SETTINGS$$');
assert.include(html, 'window.claySettings=' + toSource(settings));
});
it('Substitutes $$COMPONENTS$$ with the config', function() {
assert.notInclude(html, '$$COMPONENTS$$');
assert.include(html, 'window.clayComponents=' + toSource(clay.components));
});
it('Substitutes $$META$$ with the config', function() {
assert.notInclude(html, '$$META$$');
assert.include(html, 'window.clayMeta=' + toSource(clay.meta));
});
}); });
it('does not replace $$RETURN_TO$$ if in the emulator', function() { it('does not replace $$RETURN_TO$$ if in the emulator', function() {
var clay = fixture.clay([]); var clay = fixture.clay([]);
stubPebble(); stubPebble();
Pebble.platform = 'pypkjs'; Pebble.platform = 'pypkjs';
var decodedUrl = assert.match(decodeUrl(clay.generateUrl()), /\$\$RETURN_TO\$\$/);
atob(decodeURIComponent(clay.generateUrl().replace(/^.*?#/, '')));
assert.match(decodedUrl, /\$\$RETURN_TO\$\$/);
}); });
it('returns the emulator URL if inside emulator', function() { it('returns the emulator URL if inside emulator', function() {
@@ -186,6 +256,52 @@ describe('Clay', function() {
}); });
}); });
describe('.meta', function() {
var emptyMeta = {
activeWatchInfo: null,
accountToken: '',
watchToken: ''
};
it('populates the meta in the showConfiguration handler', function() {
stubPebble();
var clay = fixture.clay([]);
// meta only gets populated after showConfiguration happens
assert.deepEqual(clay.meta, emptyMeta);
Pebble.addEventListener.withArgs('showConfiguration').callArg(1);
assert.deepEqual(clay.meta, {
activeWatchInfo: activeWatchInfo,
accountToken: accountToken,
watchToken: watchToken
});
});
it('populates the meta in the ready handler', function() {
stubPebble();
var clay = fixture.clay([], null, {autoHandleEvents: false});
// meta only gets populated after ready happens
assert.deepEqual(clay.meta, emptyMeta);
Pebble.addEventListener.withArgs('ready').callArg(1);
assert.deepEqual(clay.meta, {
activeWatchInfo: activeWatchInfo,
accountToken: accountToken,
watchToken: watchToken
});
});
it('populates the meta with with empty values when there is no Pebble global',
function() {
delete global.Pebble;
var clay = fixture.clay([], null, {autoHandleEvents: false});
assert.deepEqual(clay.meta, emptyMeta);
});
});
describe('Clay.encodeDataUri()', function() { describe('Clay.encodeDataUri()', function() {
/** /**
+9 -1
View File
@@ -20,12 +20,20 @@ describe('ClayConfig', function() {
'build', 'build',
'on', 'on',
'off', 'off',
'trigger' 'trigger',
'meta'
]; ];
var clayConfig = fixtures.clayConfig(['input']); var clayConfig = fixtures.clayConfig(['input']);
checkReadOnly(clayConfig, properties); checkReadOnly(clayConfig, properties);
}); });
describe('.meta', function() {
it('populates meta', function() {
var clayConfig = fixtures.clayConfig(['input']);
assert.deepEqual(clayConfig.meta, fixtures.meta());
});
});
describe('throws when trying to run methods before being built', function() { describe('throws when trying to run methods before being built', function() {
[ [
'getItemByAppKey', 'getItemByAppKey',