mirror of
https://github.com/bobwen-dev/react-templates
synced 2025-04-12 00:56:39 +02:00
Created sample application
This commit is contained in:
parent
e5536fd07a
commit
576b3fcada
96
sample/ImageSearch.js
Normal file
96
sample/ImageSearch.js
Normal file
@ -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;
|
||||
});
|
18
sample/ImageSearch.rt
Normal file
18
sample/ImageSearch.rt
Normal file
@ -0,0 +1,18 @@
|
||||
<!doctype jsx InfiniteScroll="InfiniteScroll">
|
||||
<div className="innerContainer">
|
||||
<div className="searchbox">
|
||||
<input type="text" valueLink="{this.linkState('searchTerm')}" onKeyDown="(e) => if (e.keyCode == 13) { this.search(); return false; }"></input>
|
||||
<button onClick="() => this.search(); return false;">Search</button>
|
||||
</div>
|
||||
<InfiniteScroll className="fixed" onLoadMore="{this.loadMore}" threshold="{150}">
|
||||
<div rt-repeat="row in [0, 1, 2]" key="{row}">
|
||||
<a rt-repeat="i in this.state.items[row]" href="{i.originalContext}" target="blank" className="container fadeInDown" key="{i.id}">
|
||||
<div style="padding-top: {Math.floor(100/i.ratio)}%; background-color: grey"></div>
|
||||
<div className="imgContainer">
|
||||
<img width="100%" src="{i.url}"/>
|
||||
<div className="title">{i.title}</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</InfiniteScroll>
|
||||
</div>
|
57
sample/ImageSearch.rt.js
Normal file
57
sample/ImageSearch.rt.js
Normal file
@ -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))
|
||||
])));
|
||||
};
|
||||
});
|
37
sample/InfiniteScroll.js
Normal file
37
sample/InfiniteScroll.js
Normal file
@ -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;
|
||||
});
|
16
sample/index.html
Normal file
16
sample/index.html
Normal file
@ -0,0 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>React templates - Image Search Sample</title>
|
||||
<link rel="stylesheet" href="sample.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<form id="main" style="padding:20px 150px;">
|
||||
<!-- the lists panel -->
|
||||
<div id="preloader" style="text-align: center;">Loading...</div>
|
||||
</form>
|
||||
<script data-main="main.js" src="http://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.15/require.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
20
sample/main.js
Normal file
20
sample/main.js
Normal file
@ -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));
|
||||
});
|
||||
|
83
sample/sample.css
Normal file
83
sample/sample.css
Normal file
@ -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;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user