diff --git a/front-ui/app/actions/itemDetailsActions.js b/front-ui/app/actions/itemDetailsActions.js new file mode 100644 index 0000000..8fa3430 --- /dev/null +++ b/front-ui/app/actions/itemDetailsActions.js @@ -0,0 +1,36 @@ +var AppDispatcher = require('../dispatcher/appDispatcher'); +var ItemDetailsConstants = require('../constants/itemDetailsConstants'); + +// Define action methods +var ItemDetailsActions = { + + + loadItemWithDetails: function() { + AppDispatcher.handleAction({ + actionType: ItemDetailsConstants.LOAD_ITEM_WITH_DETAILS + }) + }, + + nextCarouselImage: function() { + AppDispatcher.handleAction({ + actionType: ItemDetailsConstants.NEXT_CAROUSEL_IMAGE + }) + }, + + previousCarouselImage: function() { + AppDispatcher.handleAction({ + actionType: ItemDetailsConstants.PREVIOUS_CAROUSEL_IMAGE + }) + }, + + selectCarouselImage: function(index) { + AppDispatcher.handleAction({ + actionType: ItemDetailsConstants.SELECT_CAROUSEL_IMAGE, + index: index + }) + } + + +}; + +module.exports = ItemDetailsActions; \ No newline at end of file diff --git a/front-ui/app/components/items/itemMultiMediaDescriptions.js b/front-ui/app/components/items/itemMultiMediaDescriptions.js index 1f4fb12..5b5b75b 100644 --- a/front-ui/app/components/items/itemMultiMediaDescriptions.js +++ b/front-ui/app/components/items/itemMultiMediaDescriptions.js @@ -15,9 +15,6 @@ var ItemMultimediaDescriptions = React.createClass({ ); } - //getInitialState: function () { - //return { descriptions: [] }; - //} }); diff --git a/front-ui/app/components/items/itemWithDetailsPage.js b/front-ui/app/components/items/itemWithDetailsPage.js index e76b653..295457b 100644 --- a/front-ui/app/components/items/itemWithDetailsPage.js +++ b/front-ui/app/components/items/itemWithDetailsPage.js @@ -1,8 +1,8 @@ var React = require('react'), - ItemMultiMediaDescriptions = require('./itemMultiMediaDescriptions'), - ItemActions = require('../../actions/itemActions'), - NavigationStore = require('../../stores/NavigationStore'), - ItemStore = require('../../stores/itemStore'); + Carousel = require('../shared/carousel'), + ItemDetailsActions = require('../../actions/itemDetailsActions'), + NavigationStore = require('../../stores/navigationStore'), + ItemDetailsStore = require('../../stores/itemDetailsStore'); var Router = require('react-router'); @@ -13,47 +13,59 @@ var ItemWithDetailsPage = React.createClass({ return ( - -
-
- + +
+

{this.state.item.get('name')}

- -
{this.state.item.get('list_price')} KM
-
{this.state.item.get('description')}
+
{this.state.item.get('list_price')} KM
+
{this.state.item.get('description')}
- -
-
+ +
+
quantitative descriptions
-
- ) ; +
+ ); }, // Add change listeners to stores componentDidMount: function() { - ItemStore.addChangeListener(this._onChange); + ItemDetailsStore.addChangeListener(this._onChange); NavigationStore.addChangeListener(this._onChange); - ItemActions.loadItemWithDetails(); + ItemDetailsActions.loadItemWithDetails(); }, + + onClickLeft: function() { + ItemDetailsActions.previousCarouselImage(); + + }, + + onClickRight: function() { + ItemDetailsActions.nextCarouselImage(); + }, + + onSelectImage: function(i) { + ItemDetailsActions.selectCarouselImage(i); + }, + _onChange: function () { if (this.isMounted()) { - this.setState({ - item: ItemStore.getLoadedItemWithDetails() - }); + this.setState(ItemDetailsStore.getState()); } }, getInitialState: function () { - return { - item : ItemStore.getLoadedItemWithDetails() - }; + return ItemDetailsStore.getState(); } }); diff --git a/front-ui/app/components/shared/carousel.js b/front-ui/app/components/shared/carousel.js new file mode 100644 index 0000000..69a5278 --- /dev/null +++ b/front-ui/app/components/shared/carousel.js @@ -0,0 +1,51 @@ +var React = require("react"); + +var Carousel = React.createClass({ + propTypes: { + images: React.PropTypes.array.isRequired, + selected: React.PropTypes.number.isRequired, + onClickLeft: React.PropTypes.func.isRequired, + onClickRight: React.PropTypes.func.isRequired, + onSelectImage: React.PropTypes.func.isRequired + }, + + render: function() { + var left = this.props.selected * 300 * -1, + ulStyle = { + width: this.props.images.length * 300, + "-ms-transform": "translate(" + left + "px,0px)", + "-webkit-transform": "translate(" + left + "px,0px)", + transform: "translate(" + left + "px,0px)" + }; + + return ( +
+ +
+
    + {this.props.images.map(function(image, i) { + return
  • ; + })} +
+
    + {this.props.images.map(function(image, i) { + var activeClass = i === this.props.selected ? "active" : ""; + return
  • ; + }.bind(this))} +
+
+ +
+ ) + }, + + onClickDot: function(index) { + this.props.onSelectImage(index); + } +}); + +module.exports = Carousel; \ No newline at end of file diff --git a/front-ui/app/constants/itemDetailsConstants.js b/front-ui/app/constants/itemDetailsConstants.js new file mode 100644 index 0000000..af68f91 --- /dev/null +++ b/front-ui/app/constants/itemDetailsConstants.js @@ -0,0 +1,9 @@ +var keyMirror = require('react/lib/keyMirror'); + +// Define action constants +module.exports = keyMirror({ + LOAD_ITEM_WITH_DETAILS: null, + NEXT_CAROUSEL_IMAGE: null, + PREVIOUS_CAROUSEL_IMAGE: null, + SELECT_CAROUSEL_IMAGE: null +}); \ No newline at end of file diff --git a/front-ui/app/css/carousel.css b/front-ui/app/css/carousel.css new file mode 100644 index 0000000..1e1cebd --- /dev/null +++ b/front-ui/app/css/carousel.css @@ -0,0 +1,72 @@ +* { + box-sizing: border-box; +} +.application-container { + width: 400px; + margin: 0 auto; +} +.arrow, +.circle { + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.arrow { + position: relative; + top: -100px; + padding: 10px; +} +.carousel-stage { + width: 300px; + height: 200px; + border: 1px solid black; + overflow: hidden; + display: inline-block; + position: relative; +} +.carousel-stage ul.carousel-list { + display: inline-block; + list-style: none; + padding: 0; + margin: 0; + position: relative; + transition: 300ms ease-in-out; +} +.carousel-stage ul.carousel-list li { + display: inline-block; + width: 300px; + height: 200px; + float: left; +} +.carousel-stage ul.carousel-list li img { + object-fit: cover; + object-position: center center; + width: 300px; + height: 200px; +} +.carousel-stage ul.dots { + width: 300px; + list-style: none; + padding: 0; + margin: 0; + position: absolute; + bottom: 0; + text-align: center; +} +.carousel-stage ul.dots li { + display: inline-block; + width: 14px; + height: 14px; + background-color: white; + margin: 2px; + border-radius: 50%; + line-height: 5px; + border: 2px solid black; +} +.carousel-stage ul.dots li.active { + background-color: #ccc; +} \ No newline at end of file diff --git a/front-ui/app/stores/itemDetailsStore.js b/front-ui/app/stores/itemDetailsStore.js new file mode 100644 index 0000000..f377208 --- /dev/null +++ b/front-ui/app/stores/itemDetailsStore.js @@ -0,0 +1,147 @@ +var AppDispatcher = require('../dispatcher/appDispatcher'); +var EventEmitter = require('events').EventEmitter; +var ItemDetailsConstants = require('../constants/itemDetailsConstants'); +var ItemWithDetails = require('../models/itemWithDetails'); +var _ = require('underscore'); + + +var _itemWithDetails = new ItemWithDetails(); +var _images = []; +var _currentImage = 0; +var _animating = false; +var _count; + + +var getItemIdFromUrl = function() { + // ugly but it seems to me that + // router does not want to expose its + // state (for phylosophical reasons) + var url = document.URL; + var itemIdRegex = /artikal\/(\d+)\//g; + var match = itemIdRegex.exec(url); + console.log(match); + return match[1]; +}; + +var fetchItemWithDetails = function() { + var id = getItemIdFromUrl(); + if (id !== undefined && _itemWithDetails.id !== id) { + var item = new ItemWithDetails({ + id: id + }); + item.fetch({ + success: function() { + _itemWithDetails = item; + _images = (_itemWithDetails.get("multi_media_descriptions") || []).map(function(mmd) { + return mmd.url; + }); + _count = _images.length; + _currentImage = 0; + _animating = false; + ItemWithDetailsStore.emitChange(); + } + }); + } +}; + +var handlNextImage = function() { + if (_animating) return; + var next = _currentImage + 1; + if (next >= _count) next = 0; + handleSelectImage(next); +}; + +var handlePrevImage = function() { + if (_animating) return; + var next = _currentImage - 1; + if (next < 0) next = _count - 1; + handleSelectImage(next); +}; + + +var handleSelectImage = function(index) { + if (_animating) return; + _animating = true; + _currentImage = index; + setTimeout(function() { + _animating = false; + }, 300); +}; + + + +// Extend ItemWithDetailsStore with EventEmitter to add eventing capabilities +var ItemWithDetailsStore = _.extend({}, EventEmitter.prototype, { + + getState: function() { + + + + return { + item: _itemWithDetails, + images: _images, + currentImage: _currentImage, + count: _count + } + + }, + + + // item with details + getLoadedItemWithDetails: function() { + return _itemWithDetails; + }, + + // Emit Change event + emitChange: function() { + console.log("Emitting change!"); + this.emit('change'); + }, + + // Add change listener + addChangeListener: function(callback) { + this.on('change', callback); + }, + + // Remove change listener + removeChangeListener: function(callback) { + this.removeListener('change', callback); + } + +}); + + +// Register callback with AppDispatcher +AppDispatcher.register(function(payload) { + var action = payload.action; + var text; + + switch (action.actionType) { + + case ItemDetailsConstants.LOAD_ITEM_WITH_DETAILS: + fetchItemWithDetails(); + break; + + case ItemDetailsConstants.NEXT_CAROUSEL_IMAGE: + handlNextImage(); + break; + + case ItemDetailsConstants.PREVIOUS_CAROUSEL_IMAGE: + handlePrevImage(); + break; + + case ItemDetailsConstants.SELECT_CAROUSEL_IMAGE: + handleSelectImage(action.index); + break; + + default: + return true; + } + + // If action was responded to, emit change event + ItemWithDetailsStore.emitChange(); + return true; + +}); + +module.exports = ItemWithDetailsStore; \ No newline at end of file diff --git a/front-ui/app/stores/navigationStore.js b/front-ui/app/stores/navigationStore.js index 2264eda..c9ec59b 100644 --- a/front-ui/app/stores/navigationStore.js +++ b/front-ui/app/stores/navigationStore.js @@ -8,16 +8,7 @@ var _ = require('underscore'); // Extend ItemStore with EventEmitter to add eventing capabilities var NavigationStore = _.extend({}, EventEmitter.prototype, { - getItemIdFromUrl: function () { - // ugly but it seems to me that - // router does not want to expose its - // state - var url = document.URL; - var itemIdRegex = /artikal\/(\d+)\//; - var match = itemIdRegex.exec(url); - console.log(match); - return match[0]; - }, + // Emit Change event emitChange: function() {