30 |
Max data points: {{samples}}
31 |
32 |
33 |
34 |
35 |
61 |
62 |
--------------------------------------------------------------------------------
/app/html/view/stock-graph.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
19 |
20 |
21 |
22 |
23 |
27 | Oooops
28 | Ticker symbol {{quote.Symbol}} is unknown!!
29 | Close
30 |
31 |
32 |
171 |
172 |
--------------------------------------------------------------------------------
/app/html/view/stock-search.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | Ticker symbol
38 |
39 |
40 |
Search
41 |
42 |
43 |
76 |
77 |
--------------------------------------------------------------------------------
/app/scss/app-header.scss:
--------------------------------------------------------------------------------
1 | :host header {
2 | background: hsl(210, 30%, 0%) radial-gradient(hsl(210, 30%, 20%), hsl(210, 30%, 0%));
3 | font-family: Geneva, sans-serif;
4 | margin-bottom: 30px;
5 | position: relative;
6 |
7 | #page-hits {
8 | font-family: Geneva, sans-serif;
9 | color: #fff;
10 | position: absolute;
11 | bottom: 10px;
12 | left: 10px;
13 | }
14 |
15 | /* box ------------------------------------------------------ */
16 | #box {
17 | padding: 10px;
18 | text-align: center;
19 | min-width: 500px;
20 | font-size: 3em;
21 | font-weight: bold;
22 | -webkit-backface-visibility: hidden; /* fixes flashing */
23 | }
24 |
25 | /* flashlight ------------------------------------------------------ */
26 |
27 | #flashlight {
28 | color: hsla(0, 0%, 0%, 0);
29 | perspective: 80px;
30 | outline: none;
31 | }
32 |
33 | /* flash ------------------------------------------------------ */
34 |
35 | #flash {
36 | display: inline-block;
37 | text-shadow: #bbb 0 0 1px, #fff 0 -1px 2px, #fff 0 -3px 2px, rgba(0, 0, 0, 0.8) 0 30px 25px;
38 | transition: margin-left 0.3s cubic-bezier(0, 1, 0, 1);
39 | }
40 |
41 | #box:hover #flash {
42 | margin-left: 20px;
43 | transition: margin-left 1s cubic-bezier(0, 0.75, 0, 1);
44 | }
45 |
46 | /* light ------------------------------------------------------ */
47 | #light {
48 | display: inline-block;
49 | text-shadow: #111 0 0 1px, rgba(255, 255, 255, 0.1) 0 1px 3px;
50 | }
51 |
52 | #box:hover #light {
53 | text-shadow: #fff 0 0 4px, #fcffbb 0 0 20px;
54 | transform: rotateY(-60deg);
55 | transition: transform 0.3s cubic-bezier(0, 0.75, 0, 1), text-shadow 0.1s ease-out;
56 | }
57 |
58 | & > ::content {
59 | & > .save {
60 | bottom: -10px;
61 | position: absolute;
62 | right: 0;
63 | }
64 |
65 | & > .reset {
66 | bottom: -60px;
67 | position: absolute;
68 | right: 0;
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/app/scss/stock-app.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | display: block;
3 | }
4 |
5 | #details {
6 | border: 2px solid #fff;
7 | border-radius: 3px;
8 | display: flex;
9 | flex-direction: column;
10 | justify-content: center;
11 | background-image: url(../img/background-blur.jpg);
12 | width: 400px;
13 | height: 400px;
14 | }
15 | paper-button {
16 | background: #035177;
17 | opacity: .9;
18 | color: #fff;
19 | width: 170px;
20 | z-index: 10;
21 | }
22 |
--------------------------------------------------------------------------------
/app/scss/stock-details.scss:
--------------------------------------------------------------------------------
1 | ol {
2 | color: #fff;
3 | list-style-type: none;
4 | margin: 0;
5 | padding: 0;
6 | }
7 |
8 | .label {
9 | display: inline-block;
10 | margin: 3px;
11 | text-align: right;
12 | width: 120px;
13 | }
14 | .hightlight {
15 | font-weight: 700;
16 | }
17 | .up {
18 | color: green;
19 | font-weight: 700;
20 | padding-left: 10px;
21 | text-shadow: 0px 0px 20px #fff;
22 | }
23 | .down {
24 | color: red;
25 | font-weight: 700;
26 | padding-left: 10px;
27 | text-shadow: 0px 0px 20px #fff;
28 | }
29 | .value {
30 | padding-left: 10px;
31 | }
32 |
--------------------------------------------------------------------------------
/app/scss/stock-graph-filter.scss:
--------------------------------------------------------------------------------
1 | paper-slider {
2 | width: 100%;
3 | --paper-slider-active-color: green;
4 | }
5 |
6 | paper-input-container {
7 | width: 200px;
8 | }
9 |
10 | .view-settings-wrapper {
11 | display: flex;
12 | margin: 0 20px;
13 | }
14 |
15 | .slider-wrapper {
16 | flex: 1;
17 | }
18 |
19 | .slider-wrapper label {
20 | font-family: 'Roboto', 'Noto', sans-serif;
21 | -webkit-font-smoothing: antialiased;
22 | text-rendering: optimizeLegibility;
23 | font-size: 12px;
24 | font-weight: 400;
25 | line-height: 24px;
26 | color: lightgray;
27 |
28 | display: block;
29 | margin-top: -4px;
30 | padding: 10px 0 6px 15px;
31 | height: 24px;
32 | }
--------------------------------------------------------------------------------
/app/scss/stock-search.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | display: flex;
3 | justify-content: center;
4 | }
--------------------------------------------------------------------------------
/app/scss/stock-view.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | display: flex;
3 | justify-content: center;
4 |
5 | --paper-input-container-input-color: #fff;
6 |
7 | & > article {
8 | margin-left: 30px;
9 | }
10 | }
11 | .graph {
12 | border: 2px solid #fff;
13 | border-radius: 3px;
14 | position: relative;
15 | width: 600px;
16 | height: 400px;
17 | background-image: url(../img/background-blur.jpg);
18 | }
19 | .overlay {
20 | background-color: #fff;
21 | opacity: .3;
22 | top: 0;
23 | bottom: 0;
24 | left: 0;
25 | height: 0;
26 | position: absolute;
27 | width: 100%;
28 | height: 100%;
29 | }
30 |
31 | #highcharts {
32 | border: 2px solid #fff;
33 | border-radius: 3px;
34 | }
35 |
--------------------------------------------------------------------------------
/assets/img/background-blur.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scaljeri/polymer-redux/c81e28597a4b57305dd8a26cd7aaad6d42006aa2/assets/img/background-blur.jpg
--------------------------------------------------------------------------------
/assets/img/background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scaljeri/polymer-redux/c81e28597a4b57305dd8a26cd7aaad6d42006aa2/assets/img/background.jpg
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "polymerClasses",
3 | "dependencies": {
4 | "polymer": "Polymer/polymer#^1.2",
5 | "paper-input": "PolymerElements/paper-input#~1.0.18",
6 | "paper-button": "PolymerElements/paper-button#~1.0.8",
7 | "iron-icon": "PolymerElements/iron-icon#~1.0.7",
8 | "iron-iconset-svg": "PolymerElements/iron-iconset-svg#~1.0.9",
9 | "paper-radio-button": "PolymerElements/paper-radio-button#~1.0.11",
10 | "paper-radio-group": "PolymerElements/paper-radio-group#~1.0.6",
11 | "paper-slider": "PolymerElements/paper-slider#~1.0.7",
12 | "firebase-element": "GoogleWebComponents/firebase-element#~1.0.8",
13 | "paper-behaviors": "PolymerElements/paper-behaviors#~1.0.9",
14 | "iron-localstorage": "PolymerElements/iron-localstorage#~1.0.4",
15 | "iron-a11y-keys": "PolymerElements/iron-a11y-keys#~1.0.3",
16 | "paper-dialog": "PolymerElements/paper-dialog#~1.0.2"
17 | },
18 | "devDependencies": {
19 | "es6-module-loader": "~0.17.8"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/doc/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Polymer - Redux
16 |
17 |
18 |
19 |
20 |
21 |
22 | Web Applications have changed over the years from being a simple static page to complex single page
23 | application (SPA ).
24 | With the increase of complexity, the problems to solve are much harder too. If you get some random
25 | weirdness in you SPA, you can easily bug-hunt for days.
26 |
27 | Luckily, there are two projects out there that aim at simplification
28 |
29 |
30 | Redux - a brilliant architecture which simplifies
31 | state maintenance
32 | Polymer -
33 | a back-to-nature approach that puts elements back at the center of the universe
34 |
35 |
36 |
37 | Of course frameworks like
React and
Angular help you to build complex SPAs too, but why invent whole new concepts,
39 | while the DOM itself has a lot of framework type concepts just built into it:
40 |
41 |
42 | Component model (custom element)
43 | Concept of data flow (attributes/properties and events)
44 | declarative syntax (HTML)
45 |
46 |
47 | Anyway, below I'll explain how I've used a single store, no two-way data-binding, actions and reducers in
48 | Polymer. You can visit the DEMO
here and find the code on
49 |
github
50 |
51 |
52 |
Demo site
53 |
54 | For the purpose of this exercise I've written a Stock ticker application . As you can
55 | see in the demo you can search for ticker symbols. Here are some symbols to get you started
56 |
57 |
58 | polymer - random generated data
59 | Apple - aapl
60 | Google - goog
61 | Facebook - fb
62 | Shell - rdsa.as
63 |
64 |
65 | At any time you can save the current state and hit reload without losing any data.
66 | The application will save a unique key to
67 |
LocalStorage which is used
68 | to restore the state from
69 |
Firebase
70 | With Redux you almost get this functionality for free :)
71 |
72 | NOTE: If the demo doesn't work, make sure you're using an up
2 date browser,
73 | like
Chrome Canary !!
74 |
75 |
76 |
Single source of truth
77 |
78 |
79 | In Polymer everything is an element, for example, an
80 | AJAX call can be expressed in HTML.
81 | There is an element for everything.
82 |
83 |
84 | So, in the main component of the demo app
85 | (stock-app.html )
86 | I connect the reducers with the store
87 |
88 |
< app-store id="store"
89 | state="{{state}}"
90 | reducer="[[reducer]]" > </ app-store >
91 |
92 | I pass the
reducers to the store and bind the state (child-to-host)
{{state}} .
93 | From there on I pass state properties to child elements by one-way data-binding only.
94 |
95 |
< app-header hits="[[state.page.hit]]" >
96 | ...
97 | </ app-header >
98 |
99 |
100 |
101 |
Actions
102 |
103 |
104 | Through attributes components receive parts of the state. In order to change the state
105 | each component includes the store and can dispatches actions
106 |
107 |
< app-store id="store" > </ app-store >
108 | ...
109 | this.$.store.dispatch('CHANGE_QUERYSTRING',
110 | {queryString: this.queryString});
111 |
112 | However, everytime you include the store a new instance is created. So I've turned
113 | the
app-store.html
114 | into a singleton.
115 |
116 |
117 |
118 |
Reducers
119 |
120 |
121 | Reducers are pure function (wrapped in an element of course). They don't change the state
122 | object, but return a complete new state object
123 |
124 |
class QuoteReducer {
125 | transform(state, action, input) {
126 | switch (action) {
127 | case ' QUOTE_CHANGE ' :
128 | return Object.assign({}, state,
129 | {quote: input.quote});
130 | default :
131 | return state;
132 | }
133 | }
134 | }
135 |
136 | I've tried to simplify the reducers even more by using
137 |
Immutable.js , but that didn't
138 | work very well, because it breaks Polymer's data-binding.
139 |
140 | So finally, when the reducers are ready, the store updates its state and Polymer makes sure
141 | that the changes flow (from top to bottom) through the application.
142 |
143 |
144 |
Conclusion
145 |
146 |
147 | It turns out that these two great project work very well together. Unfortunately
148 | at the moment you need to do some additional work to create a Singleton and you
149 | cannot use Immutable.js.
150 |
151 |
152 |
153 |
154 |
169 |
170 |
171 |
--------------------------------------------------------------------------------
/doc/main.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'Antic', sans-serif;
3 | font-size: 16px;
4 | margin: 0;
5 | padding: 0;
6 | }
7 |
8 | header {
9 | display: flex;
10 | justify-content: center;
11 | width: 100%;
12 | height: 120px;
13 | position: fixed;
14 | background-color: #235653;
15 | transition: all 0.4s ease-in-out;
16 | }
17 |
18 | img {
19 | height: 100px;
20 | margin: 10px 0;
21 | transition: all 0.4s ease-in-out;
22 | }
23 |
24 | h1 {
25 | padding: 0;
26 | margin:0;
27 | color: #fff;
28 | font-weight: 700;
29 | font-size: 50px;
30 | margin-left: 10px;
31 | line-height: 120px;
32 | transition: all 0.4s ease-in-out;
33 | }
34 |
35 | header.minified {
36 | height: 30px;
37 | }
38 |
39 | header.minified h1 {
40 | font-size: 15px;
41 | line-height: 30px;
42 | }
43 |
44 | header.minified img {
45 | height: 20px;
46 | margin: 6px 0;
47 | }
48 |
49 | article {
50 | display: flex;
51 | justify-content: center;
52 | margin-bottom: 50px;
53 | padding-top: 150px;
54 | }
55 |
56 | div.content-container {
57 | max-width: 600px;
58 | overflow: hidden;
59 | }
60 |
61 | h2 {
62 | color: #e63a74;
63 | font-size: 2rem;
64 | }
65 |
66 | p {
67 | font-size: 1rem; /* 12px */
68 | line-height: 1.5000rem; /* 18px */
69 | margin: 0 0 1.5000rem 0;
70 | }
71 |
72 | .up2date {
73 | display: inline-block;
74 | position: relative;
75 | top: -2px;
76 | }
77 |
78 | ol {
79 | margin-left: 50px;
80 | }
81 |
82 | @media screen and (min-width: 650px) {
83 | /* Drop Caps */
84 | p::first-letter {
85 | float: left;
86 | color: #758afe;
87 | font-size: 5rem;
88 | line-height: 60px;
89 | padding-top: 4px;
90 | padding-right: 8px;
91 | padding-left: 3px;
92 | font-family: Georgia;
93 | }
94 |
95 | p.first-dark-blue::first-letter {
96 | color: #273380;
97 | }
98 |
99 | p.first-dark-red::first-letter {
100 | color: #802041 !important;
101 | }
102 | }
103 |
104 | pre {
105 | display: inline;
106 | background-color: lightgray;
107 | margin-right: 5px;
108 | }
109 | pre.demo-code {
110 | background-color: #fff;
111 | color:#000000;
112 | display: block;
113 | margin-left: 50px;
114 | }
115 |
116 | @media screen and (max-width: 650px) {
117 | header {
118 | height: 30px;
119 | }
120 |
121 | h1 {
122 | font-size: 15px;
123 | line-height: 30px;
124 | }
125 |
126 | img {
127 | height: 20px;
128 | margin: 6px 0;
129 | }
130 |
131 | article {
132 | display: flex;
133 | justify-content: center;
134 | margin-bottom: 50px;
135 | padding-top: 50px;
136 | }
137 |
138 | div {
139 | margin-left: 10px;
140 | margin-right: 10px;
141 | }
142 |
143 | pre.demo-code {
144 | margin-left: 0;
145 | margin-right: 0;
146 | }
147 | }
148 |
149 | @media screen and (max-width: 451px) {
150 | header, section, div {
151 | position: relative;
152 | width: 450px;
153 | overflow: scroll!important;
154 | }
155 |
156 | article {
157 | padding-top: 10px;
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/doc/polymer-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
--------------------------------------------------------------------------------
/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scaljeri/polymer-redux/c81e28597a4b57305dd8a26cd7aaad6d42006aa2/favicon.png
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var $,
2 | fs = require('fs'),
3 | del = require('del'),
4 | gulp = require('gulp'),
5 | path = require('path'),
6 | clean = require('gulp-clean'),
7 | runSequence = require('run-sequence'),
8 | gulpLoadPlugins = require('gulp-load-plugins');
9 |
10 | $ = gulpLoadPlugins();
11 |
12 | gulp.task('default', ['clean'], function () {
13 | runSequence('copy', 'js', 'inject-css');
14 | });
15 |
16 | gulp.task('clean', function () {
17 | del.sync('./dist/');
18 | });
19 |
20 | gulp.task('watch', ['clean'], function() {
21 | runSequence('copy', 'js', 'inject-css', function () {
22 | return gulp.watch(['app/**/*.{js,html,scss}'], function () {
23 | runSequence('clean', 'copy', 'js', 'inject-css');
24 | });
25 | });
26 | });
27 |
28 | gulp.task('copy', function() {
29 | return gulp.src('assets/img/*.*')
30 | .pipe(gulp.dest('./dist/img'));
31 | });
32 |
33 | gulp.task('js', function() {
34 | return gulp.src(['app/**/*.{js,html}'])
35 | .pipe($.sourcemaps.init())
36 | .pipe($.if('*.html', $.crisper())) // Extract JS from .html files
37 | .pipe($.if('*.js', $.babel()))
38 | .pipe($.sourcemaps.write('.'))
39 | .pipe(gulp.dest('.tmp/'))
40 | .pipe(gulp.dest('dist/'));
41 | });
42 |
43 | gulp.task('sass', function() {
44 | return gulp.src('./app/scss/**/*.scss')
45 | .pipe($.sass().on('error', $.sass.logError))
46 | .pipe(gulp.dest('./dist/css'));
47 | });
48 |
49 |
50 | gulp.task('inject-css', ['sass'], function() {
51 | return gulp.src('./dist/html/**/*.html', {base: './dist/html'})
52 | .pipe($.tap(function(file, t) {
53 | var filename = path.basename(file.path).replace(/html$/, 'css');
54 |
55 | return gulp.src(file.path, {base: './dist/html'})
56 | .pipe($.replace(/\/\* inject:css \*\//,
57 | function() {
58 | var style = fs.readFileSync('./dist/css/' + filename, 'utf8');
59 | return style;
60 | }))
61 | .pipe(clean())
62 | .pipe(gulp.dest('./dist/html'));
63 | }));
64 | });
65 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
Polymer classes in ES6
8 |
9 |
10 |
11 |
12 |
13 |
14 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "app",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "gulp": "^3.9.0",
13 | "run-sequence": "^1.1.4"
14 | },
15 | "devDependencies": {
16 | "del": "^2.0.2",
17 | "gulp-babel": "^5.2.1",
18 | "gulp-clean": "^0.3.1",
19 | "gulp-crisper": "0.0.6",
20 | "gulp-if": "^2.0.0",
21 | "gulp-inject": "^3.0.0",
22 | "gulp-load-plugins": "^1.0.0-rc.1",
23 | "gulp-replace": "^0.5.4",
24 | "gulp-sass": "^2.0.4",
25 | "gulp-sourcemaps": "^1.6.0",
26 | "gulp-tap": "^0.1.3",
27 | "vinyl-paths": "^2.0.0"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------