diff --git a/sample/ImageSearch.js b/sample/ImageSearch.js new file mode 100644 index 0000000..3a48630 --- /dev/null +++ b/sample/ImageSearch.js @@ -0,0 +1,96 @@ +define([ + 'lodash', + 'jquery', + 'react', + 'ImageSearch.rt' +], function (_, $, React, template) { + 'use strict'; + + var ImageSearch = React.createClass({ + + displayName: 'ImageSearch', + mixins: [React.addons.LinkedStateMixin], + + seq: 0, + total: 0, + hasMore: true, + heights: [0, 0, 0], + realTerm: 'cats', + + getInitialState: function () { + setTimeout(this.search, 0); + return { + searchTerm: this.realTerm, + items: [[], [], []] + }; + }, + + search: function() { + this.state.items = [[], [], []]; + this.total = 0; + this.heights = [0, 0, 0]; + this.hasMore = true; + this.realTerm = this.state.searchTerm; + this.loadMore(); + }, + + indexOfMin: function(array) { + var indexAndMin = _.reduce(array, function(accum, height, index) { + return (height < accum.min) ? { i: index, min: height } : accum; + }, {i: -1, min: Number.MAX_VALUE}); + return indexAndMin.i; + }, + + loadMore: function(done) { + done = done || function() {}; + if (!this.hasMore) { + done(); + return; + } + var url = 'https://ajax.googleapis.com/ajax/services/search/images?v=1.0&rsz=8&start=' + this.total + '&q=' + this.realTerm + "&callback=?"; + + var self = this; + $.ajax({url: url, dataType: 'jsonp'}) + .done(function(data){ + if (!data.responseData) { + self.hasMore = false; + done(); + return; + } + var results = data.responseData.results; + + var items = _.cloneDeep(self.state.items); + + for (var i = 0; i < results.length; i++) { + var result = data.responseData.results[i]; + var minHeightIndex = self.indexOfMin(self.heights); + + items[minHeightIndex].push({ + id: self.seq + 1, + title: result.titleNoFormatting, + url: result.url, + ratio: result.width / result.height, + originalContext: result.originalContextUrl + }); + + var relativeHeight = result.height / result.width; + self.heights[minHeightIndex] = self.heights[minHeightIndex] + relativeHeight; + self.total++; + self.seq++; + } + self.setState({items: items}); + done(); + }); + }, + + shouldComponentUpdate: function(nextProps, nextState) { + return !_.isEqual(this.state, nextState); + }, + + render: function () { + return template.apply(this); + } + }); + + return ImageSearch; +}); \ No newline at end of file diff --git a/sample/ImageSearch.rt b/sample/ImageSearch.rt new file mode 100644 index 0000000..de43c9b --- /dev/null +++ b/sample/ImageSearch.rt @@ -0,0 +1,18 @@ + +
+
+ + +
+ +
+ +
+
+ +
{i.title}
+
+
+
+
+
\ No newline at end of file diff --git a/sample/ImageSearch.rt.js b/sample/ImageSearch.rt.js new file mode 100644 index 0000000..9472194 --- /dev/null +++ b/sample/ImageSearch.rt.js @@ -0,0 +1,57 @@ +define([ + 'react', + 'lodash', + 'InfiniteScroll' +], function (React, _, InfiniteScroll) { + 'use strict'; + function onKeyDown1(e) { + if (e.keyCode == 13) { + this.search(); + return false; + } + } + function onClick2() { + this.search(); + return false; + } + function repeatI3(row, rowIndex, i, iIndex) { + return React.DOM.a({ + 'href': i.originalContext, + 'target': 'blank', + 'className': 'container fadeInDown', + 'key': i.id + }, React.DOM.div({ + 'style': { + paddingTop: Math.floor(100 / i.ratio) + '%', + backgroundColor: 'grey' + } + }), React.DOM.div({ 'className': 'imgContainer' }, React.DOM.img({ + 'width': '100%', + 'src': i.url + }), React.DOM.div({ 'className': 'title' }, i.title))); + } + function repeatRow4(row, rowIndex) { + return React.DOM.div.apply(this, _.flatten([ + { 'key': row }, + _.map(this.state.items[row], repeatI3.bind(this, row, rowIndex)) + ])); + } + return function () { + return React.DOM.div({ 'className': 'innerContainer' }, React.DOM.div({ 'className': 'searchbox' }, React.DOM.input({ + 'type': 'text', + 'valueLink': this.linkState('searchTerm'), + 'onKeyDown': onKeyDown1.bind(this) + }), React.DOM.button({ 'onClick': onClick2.bind(this) }, 'Search')), InfiniteScroll.apply(this, _.flatten([ + { + 'className': 'fixed', + 'onLoadMore': this.loadMore, + 'threshold': 150 + }, + _.map([ + 0, + 1, + 2 + ], repeatRow4.bind(this)) + ]))); + }; +}); \ No newline at end of file diff --git a/sample/InfiniteScroll.js b/sample/InfiniteScroll.js new file mode 100644 index 0000000..8502e02 --- /dev/null +++ b/sample/InfiniteScroll.js @@ -0,0 +1,37 @@ +define([ + 'lodash', + 'jquery', + 'react' +], function (_, $, React) { + 'use strict'; + + var InfiniteScroll = React.createClass({ + + displayName: 'InfiniteScroll', + gettingMore: false, + + onLoadMoreFinished: function() { + this.gettingMore = false; + }, + + onScroll: function(evt) { + if (!this.props.onLoadMore || this.gettingMore) { + return; + } + var threshold = this.props.threshold || 0; + var raw = evt.target; + if (raw.scrollTop + raw.offsetHeight + threshold >= raw.scrollHeight) { + this.gettingMore = true; + this.props.onLoadMore(this.onLoadMoreFinished); + } + }, + + render: function () { + var passedProps = _.omit(this.props, ['onLoadMore', 'threshold', 'children', 'onScroll']); + passedProps.onScroll = this.onScroll; + return React.DOM.div(passedProps, this.props.children); + } + }); + + return InfiniteScroll; +}); diff --git a/sample/index.html b/sample/index.html new file mode 100644 index 0000000..1a37c78 --- /dev/null +++ b/sample/index.html @@ -0,0 +1,16 @@ + + + + + React templates - Image Search Sample + + + +
+ +
Loading...
+
+ + + + diff --git a/sample/main.js b/sample/main.js new file mode 100644 index 0000000..b5c19c4 --- /dev/null +++ b/sample/main.js @@ -0,0 +1,20 @@ +'use strict'; + +requirejs.config({ +// baseUrl: '/', + paths: { + lodash: 'http://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash', + jquery: 'http://code.jquery.com/jquery-1.11.0.min', + react: 'http://fb.me/react-with-addons-0.12.0' + }, + shim: { + lodash: { exports: '_' }, + jquery: { exports: '$' }, + react: { exports: 'React' } + } +}); + +requirejs(['jquery', 'react', 'ImageSearch'], function ($, React, ImageSearch) { + React.renderComponent(ImageSearch(), $('#main').get(0)); +}); + diff --git a/sample/sample.css b/sample/sample.css new file mode 100644 index 0000000..e45f98f --- /dev/null +++ b/sample/sample.css @@ -0,0 +1,83 @@ +.imgContainer { + position:absolute; + top:0; + bottom:0; + left:0; + right:0; +} + +.fixed { + height: calc(553px - 53px); + overflow-y: auto; + overflow-x: hidden; +} + +.fixed > div { + width: 33%; + display: inline-block; + vertical-align: top; +} + +.searchbox { + text-align:center; + height: 53px; +} + +.container { + display: block; + background-color: gray; + position: relative; + margin: 0 10px 10px 0; +} + +@-webkit-keyframes fadeInDown{ + 0%{ + opacity:0; + -webkit-transform:translate3d(0,-50%,0); + transform:translate3d(0,-50%,0) + } + + 100%{ + opacity:1; + -webkit-transform:none; + transform:none + } + +} + +@keyframes fadeInDown{ + 0%{ + opacity:0; + -webkit-transform:translate3d(0,-50%,0); + -ms-transform:translate3d(0,-50%,0); + transform:translate3d(0,-50%,0) + } + + 100%{ + opacity:1; + -webkit-transform:none; + -ms-transform:none; + transform:none + } + +} + +.fadeInDown{ + -webkit-animation:fadeInDown 1s; + animation:fadeInDown 1s; +} + +.title { + color: white; + background-color: black; + font-family: Arial, Helvetica, sans-serif; + font-size: 9pt; + opacity: 0; + position: absolute; + bottom:0; +} + +.container:hover .title { + opacity: 1; + transition: opacity 0.75s; +}