diff --git a/README.md b/README.md index 49b8660..0edf6c7 100644 --- a/README.md +++ b/README.md @@ -477,7 +477,7 @@ One shop to rule them all! - + diff --git a/front-ui/app/actions/cartActions.js b/front-ui/app/actions/cartActions.js index 24fd87f..68c6f85 100644 --- a/front-ui/app/actions/cartActions.js +++ b/front-ui/app/actions/cartActions.js @@ -65,6 +65,13 @@ var CartActions = { itemId: itemId, count: count }); + }, + + setAddressColapsed: function(isColapsed) { + AppDispatcher.handleAction({ + actionType: CartConstants.SET_ADDRESS_COLAPSED, + isColapsed: isColapsed + }); } }; diff --git a/front-ui/app/components/cart/checkoutPage.js b/front-ui/app/components/cart/checkoutPage.js index 523f2d4..7eb1db3 100644 --- a/front-ui/app/components/cart/checkoutPage.js +++ b/front-ui/app/components/cart/checkoutPage.js @@ -17,9 +17,9 @@ var CheckoutPage = React.createClass({ render: function() { - var supportedPlaceOptions = CartStore.getSupportedPlaces().map ( function (p) { return ()}) + var supportedPlaceOptions = CartStore.getSupportedPlaces().map ( function (p) { return ()}); - return ( + var content = (
@@ -88,7 +88,35 @@ var CheckoutPage = React.createClass({
- ); + ); + + if(CartStore.isAddressColapsed()) { + + var address = CartStore.getHumanReadableAddress().map(function (a) { return ({a}
)}); + content = ( +
+

Roba će biti dostavljena na adresu:

+

+ {address} +
+ Ukupno: + +

+

+ ili +

+
+ +
+
+
+
+
+
); + } + + + return content; }, @@ -116,6 +144,10 @@ var CheckoutPage = React.createClass({ CartActions.confirmDelivery(); }, + _onUncolapseClick: function (event) { + CartActions.setAddressColapsed(false); + }, + getInitialState: function () { return CartStore.getWholeCartState(); } diff --git a/front-ui/app/constants/cartConstants.js b/front-ui/app/constants/cartConstants.js index 4e37b80..af879a7 100644 --- a/front-ui/app/constants/cartConstants.js +++ b/front-ui/app/constants/cartConstants.js @@ -9,5 +9,6 @@ module.exports = keyMirror({ CONFIRM_DELIVERY: null, SET_ITEM_COUNT: null, ADD_N_ITEMS: null, - REMOVE_ITEM: null + REMOVE_ITEM: null, + SET_ADDRESS_COLAPSED: null }); diff --git a/front-ui/app/stores/cartStore.js b/front-ui/app/stores/cartStore.js index 46907ae..8d7d604 100644 --- a/front-ui/app/stores/cartStore.js +++ b/front-ui/app/stores/cartStore.js @@ -25,6 +25,8 @@ var _deliveryCosts = new Place({ postalCode: _deliveryDestination.get('place') }) +var _addressColapsed = false; + var supportedPlaces = [ { @@ -82,6 +84,16 @@ var supportedPlaces = [ var _cartDataLoadCalled = false; +var nameOfThePlace = function(code) { + for(var i=0; iKategorije +
+ {(this.state.section.get('categories') || []).map(function(category){ + return ( +
+ + + + +
+ {category.name} +
+
+
    + {category.sub_categories.map(function(sc) { + return ( +
  • + {sc.name} +
  • + ) + })} +
+ +
+ ) + })} +
*/ + + ) + ) + ) + }, + onCategoryClick: function(category, section) { + NavigationActions.goToCategory(new Category(category), section); + + }, + componentWillReceiveProps: function(nextProps) { + var sectionId = this.getParams().id; + ItemActions.loadBestSellingItemsForSection(sectionId); + SectionActions.loadSectionDetails(sectionId); + }, + componentDidMount: function() { + + var sectionId = this.getParams().id; + ItemActions.loadBestSellingItemsForSection(sectionId); + SectionActions.loadSectionDetails(sectionId); + + SectionStore.addChangeListener(this._onSectionChange); + ItemStore.addChangeListener(this._onChange); + }, + componentWillUnmount: function() { + SectionStore.removeChangeListener(this._onSectionChange); + ItemStore.removeChangeListener(this._onChange); + }, + _onSectionChange: function() { + if(this.isMounted()) { + this.setState({ + section: SectionStore.getSectionDetails() + }); + } + }, + _onChange: function() { + if(this.isMounted()) { + + this.setState({items: ItemStore.getBestSellingForSection()}); + } + } +}); + +module.exports = BySection; + + +},{"../../actions/itemActions.js":"/home/senadu/projects/ribica/front-ui/app/actions/itemActions.js","../../actions/navigationActions":"/home/senadu/projects/ribica/front-ui/app/actions/navigationActions.js","../../actions/sectionActions.js":"/home/senadu/projects/ribica/front-ui/app/actions/sectionActions.js","../../models/category":"/home/senadu/projects/ribica/front-ui/app/models/category.js","../../models/itemCollection":"/home/senadu/projects/ribica/front-ui/app/models/itemCollection.js","../../models/section":"/home/senadu/projects/ribica/front-ui/app/models/section.js","../../stores/itemStore":"/home/senadu/projects/ribica/front-ui/app/stores/itemStore.js","../../stores/navigationStore":"/home/senadu/projects/ribica/front-ui/app/stores/navigationStore.js","../../stores/sectionStore":"/home/senadu/projects/ribica/front-ui/app/stores/sectionStore.js","../items/itemList":"/home/senadu/projects/ribica/front-ui/app/components/items/itemList.js","../linkBanner/linkBanner":"/home/senadu/projects/ribica/front-ui/app/components/linkBanner/linkBanner.js","react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js","react-router":"/home/senadu/projects/ribica/front-ui/node_modules/react-router/modules/index.js"}],"/home/senadu/projects/ribica/front-ui/app/components/browsing/bySubCategory.js":[function(require,module,exports){ +var React = require('react'), + Router = require('react-router'); + +var BySubCategoryActions = require('../../actions/bySubCategoryActions'); +var BySubCategoryStore = require('../../stores/bySubCategoryStore'); +var ItemStore = require('../../stores/itemStore'); +var CategoryStore = require('../../stores/categoryStore'); + +var Globals = require('../../globals'); +var FilterCriteriaSelector = require('./filterCriteriaSelector'); +var AppliedFiltersList = require('./appliedFiltersList'); + +var NavigationActions = require('../../actions/navigationActions'); +var ItemList = require('../items/itemList'); +var BySubCategory = React.createClass({displayName: "BySubCategory", + mixins: [Router.State], + getInitialState : function() { + return BySubCategoryStore.getState(); + }, + onFCClick: function(fc, fcv) { + BySubCategoryActions.filterCriteriaClick(fc, fcv); + }, + removeAppliedFilter: function(name) { + BySubCategoryActions.removeAppliedFilter(name); + }, + onChangePage: function(page) { + BySubCategoryActions.changePage(page); + }, + render : function() { + + return (React.createElement("div", null, + React.createElement("div", {className: "col-md-2"}, + React.createElement(FilterCriteriaSelector, {filterCriterias: this.state.subCategory.get('filter_criterias'), onFCClick: this.onFCClick}) + ), + React.createElement("div", {classname: "col-md-10"}, + + React.createElement("h2", null, + this.state.subCategory.get('name') + ), + + React.createElement("div", null, + React.createElement(AppliedFiltersList, {filters: this.appliedSubCategoryFiltersArray(), onRemove: this.removeAppliedFilter}) + + ), + React.createElement(ItemList, {items: this.state.items, paginationEnabled: true, total: this.state.items.totalCount, limit: this.state.pagination.limit, onPageChange: this.onChangePage, currentOffset: this.state.pagination.offset}) + ) + )) + }, + appliedSubCategoryFiltersArray: function() { + var filters = []; + for(var key in this.state.filter) { + if(this.state.filter.hasOwnProperty(key) && key !== 'limit' && key !== 'offset') { + filters.push({name: key, value: this.state.filter[key]}); + } + } + return filters; + }, + update: function() { + var subCategoryId = this.getParams().id; + var filter = this.getQuery(); + var offset = filter.offset || 0; + var limit = filter.limit || Globals.DefaultPageSize; + + BySubCategoryActions.load(subCategoryId, offset, limit, filter); + }, + componentWillReceiveProps: function() { + this.update(); + }, + componentDidMount: function() { + BySubCategoryStore.addChangeListener(this._onChange); + this.update(); + }, + componentWillUnmount: function() { + BySubCategoryStore.removeChangeListener(this._onChange); + }, + _onChange: function() { + if(this.isMounted()) { + this.setState(BySubCategoryStore.getState()); + } + } +}); + +module.exports = BySubCategory; + + +},{"../../actions/bySubCategoryActions":"/home/senadu/projects/ribica/front-ui/app/actions/bySubCategoryActions.js","../../actions/navigationActions":"/home/senadu/projects/ribica/front-ui/app/actions/navigationActions.js","../../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","../../stores/bySubCategoryStore":"/home/senadu/projects/ribica/front-ui/app/stores/bySubCategoryStore.js","../../stores/categoryStore":"/home/senadu/projects/ribica/front-ui/app/stores/categoryStore.js","../../stores/itemStore":"/home/senadu/projects/ribica/front-ui/app/stores/itemStore.js","../items/itemList":"/home/senadu/projects/ribica/front-ui/app/components/items/itemList.js","./appliedFiltersList":"/home/senadu/projects/ribica/front-ui/app/components/browsing/appliedFiltersList.js","./filterCriteriaSelector":"/home/senadu/projects/ribica/front-ui/app/components/browsing/filterCriteriaSelector.js","react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js","react-router":"/home/senadu/projects/ribica/front-ui/node_modules/react-router/modules/index.js"}],"/home/senadu/projects/ribica/front-ui/app/components/browsing/filterCriteriaSelector.js":[function(require,module,exports){ +var React = require('react'), + Router = require('react-router'); + +var FilterCriteriaSelector = React.createClass({displayName: "FilterCriteriaSelector", + render : function() { + var self = this; + return (React.createElement("div", null, + + this.props.filterCriterias.map(function(fc) { + return (React.createElement("div", null, + + React.createElement("div", {className: "h4", style: {color: '#cd3071'}}, fc.title), + React.createElement("ul", null, + fc.filter_criteria_values.map(function(fcv) { + return (React.createElement("li", null, + React.createElement("a", {onClick: self.props.onFCClick.bind(self,fc, fcv)}, fcv.filter_text) + )) + }) + ) + )) + }) + + )) + + } +}); + +module.exports = FilterCriteriaSelector; + + +},{"react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js","react-router":"/home/senadu/projects/ribica/front-ui/node_modules/react-router/modules/index.js"}],"/home/senadu/projects/ribica/front-ui/app/components/cart/addToCart.js":[function(require,module,exports){ +var React = require('react'); +var CartStore = require('../../stores/cartStore.js'); +var CartActions = require('../../actions/cartActions.js'); +var Globals = require('../../globals'); + +var buttonHolderStyle = { + display: 'inline-block' +}; + +var AddToCart = React.createClass({displayName: "AddToCart", + INITIAL_ITEM_COUNT: 1, + render: function() { + var itemCount = this.state.count; + var amountAndAddButton = ( + React.createElement("div", {className: "row-fluid add-to-cart"}, + React.createElement("div", {className: "col-lg-12"}, + React.createElement("button", {className: "btn white_button", onClick: this._onDecreaseClick}, "-"), React.createElement("span", {className: "add-to-cart-count"}, itemCount), + React.createElement("button", {className: "btn white_button", onClick: this._onIncreaseClick}, "+") + ), + React.createElement("div", null, + React.createElement("div", {style: buttonHolderStyle}, React.createElement("button", {className: "btn add-to-cart-button", onClick: this._addToCartClick}, "Ubaci u korpu")) + ) + ) + ); + return amountAndAddButton; + }, + + // Add change listeners to stores + componentDidMount: function() { + CartStore.addChangeListener(this._onChange); + + if(!CartStore.dataStartedLoading()) { + CartActions.load(); + }; + }, + + + getInitialState: function() { + var itemInCart = CartStore.getStateFor(this.props.item.get('id')); + return { + item: itemInCart, + count: this.INITIAL_ITEM_COUNT + } + }, + + + _onChange: function () { + if (this.isMounted()) { + var item = CartStore.getStateFor(this.props.item.get('id')); + this.setState({ item: item, count: this.INITIAL_ITEM_COUNT }); + } + }, + + _onIncreaseClick: function () { + + if (this.state.count < Globals.MaxNumberOfItemsToBeAdded ) { + this.state.count = this.state.count + 1; + this.setState(this.state); + } + }, + + _onDecreaseClick: function () { + + if (this.state.count > 1) { + this.state.count = this.state.count - 1; + this.setState(this.state); + } + }, + + _addToCartClick: function () { + CartActions.addNItems(this.props.item, this.state.count); + }, + + componentWillUnmount: function () { + CartStore.removeChangeListener(this._onChange); + } + +}); + +module.exports = AddToCart; + + +},{"../../actions/cartActions.js":"/home/senadu/projects/ribica/front-ui/app/actions/cartActions.js","../../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","../../stores/cartStore.js":"/home/senadu/projects/ribica/front-ui/app/stores/cartStore.js","react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js"}],"/home/senadu/projects/ribica/front-ui/app/components/cart/cartIcon.js":[function(require,module,exports){ +var React = require('react'); +var CartStore = require('../../stores/cartStore.js'); +var CartActions = require('../../actions/cartActions.js'); +var NavigationActions = require('../../actions/navigationActions.js'); +// var LoginStatus = require('../shared/loginStatus'); +CartTotal = require('./cartTotal'); + + + + +var cartStyle = { + fontSize: '50px' +}; + +var normalizeCount = function(count) { + if (count >= 0 && count < 10) { + return "\u00a0" + count; + } else { + return count; + } +} + +var CartIcon = React.createClass({displayName: "CartIcon", + + render: function() { + + var textNotificationStyle = (this.state.count > 0) ? { display: 'inline-block'} : { display: 'none'} ; + + return ( + React.createElement("div", null, + React.createElement("ul", {className: "nav navbar-nav navbar-right hidden-md hidden-sm hidden-xs"}, + React.createElement("li", {onClick: this._onClick, style: {borderTop: 'solid lightgray 1px', borderBottom: 'solid lightgray 1px', borderLeft: 'solid lightgray 1px', paddingBottom: 22}}, React.createElement("a", null, React.createElement("div", {className: "mycart"}, React.createElement("span", null, normalizeCount(this.state.count))))), + React.createElement("li", {onClick: this._onClick, style: {borderTop: 'solid lightgray 1px', borderBottom: 'solid lightgray 1px', paddingBottom: 2}}, React.createElement("a", {href: "#", style: { paddingRight: '5px', backgroundColor: 'transparent'}}, React.createElement(CartTotal, {items: this.state.items, itemCounts: this.state.itemCounts, deliveryCosts: this.state.deliveryCosts, justMerchandise: true}), " ")), + React.createElement("li", {onClick: this._onClick, style: {borderTop: 'solid lightgray 1px', borderBottom: 'solid lightgray 1px', borderRight: 'solid lightgray 1px'}}, + React.createElement("a", {style: {marginBottom: 10, marginRight: 10}, className: "mybutton", href: "#"}, "Završi narudžbu")) + + ), + + React.createElement("ul", {className: "nav navbar-nav navbar-right hidden-lg"}, + React.createElement("li", {onClick: this._onClick, style: {borderTop: 'solid lightgray 1px', borderBottom: 'solid lightgray 1px', borderLeft: 'solid lightgray 1px', paddingBottom: 22}}, React.createElement("a", null, React.createElement("div", {className: "mycart"}, React.createElement("span", null, normalizeCount(this.state.count))))), + React.createElement("li", {onClick: this._onClick, style: {borderTop: 'solid lightgray 1px', borderBottom: 'solid lightgray 1px',borderRight: 'solid lightgray 1px', paddingBottom: 2}}, React.createElement("a", {href: "#", style: { paddingRight: '5px', backgroundColor: 'transparent'}}, React.createElement(CartTotal, {items: this.state.items, itemCounts: this.state.itemCounts, deliveryCosts: this.state.deliveryCosts, justMerchandise: true}), " ")) + ) + ) + ); + }, + + // Add change listeners to stores + componentDidMount: function() { + CartStore.addChangeListener(this._onChange); + if(!CartStore.dataStartedLoading()) { + CartActions.load(); + }; + }, + + + getInitialState: function() { + var cartState = CartStore.getWholeCartState(); + return cartState; + }, + + _onChange: function () { + if (this.isMounted()) { + + this.setState(CartStore.getWholeCartState()); + } + }, + + componentWillUnmount: function () { + CartStore.removeChangeListener(this._onChange); + }, + + _onClick: function(e) { + NavigationActions.goToCart(); + e.preventDefault(); + } + + +}); + +module.exports = CartIcon; + + +},{"../../actions/cartActions.js":"/home/senadu/projects/ribica/front-ui/app/actions/cartActions.js","../../actions/navigationActions.js":"/home/senadu/projects/ribica/front-ui/app/actions/navigationActions.js","../../stores/cartStore.js":"/home/senadu/projects/ribica/front-ui/app/stores/cartStore.js","./cartTotal":"/home/senadu/projects/ribica/front-ui/app/components/cart/cartTotal.js","react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js"}],"/home/senadu/projects/ribica/front-ui/app/components/cart/cartPage.js":[function(require,module,exports){ +var React = require('react'), + CartStore = require('../../stores/cartStore'), + AddToCart = require('../cart/addToCart'), + CartActions = require('../../actions/cartActions'), + NavigationActions = require('../../actions/navigationActions'), + SingleItem = require('../items/singleItem'), + Globals = require('../../globals'), + LinkBanner = require('../linkBanner/linkBanner'), + CartTotal = require('./cartTotal'); + AllItemsInGroup = require('../items/allItemsInGroup'); + + +var Router = require('react-router'); + +var CartPage = React.createClass({displayName: "CartPage", + _onTakeItemOut: function(itemId) { + CartActions.takeItemOut(itemId); + }, + render: function() { + var counts = this.state.itemCounts; + var self = this; + var displayedItems = this.state.items.filter(function(i) { + if(!counts) return false; + var count = counts[i.get('id')].get('count'); + return count > 0 || count === ""; + }).map(function (i) { + var count = counts[i.get('id')].get('count'); + var price = i.get('list_price'); + var firstImage = i.get('multi_media_descriptions')[0]; + firstImage = firstImage || { resized_url: "https://res.cloudinary.com/lfvt7ps2n/image/upload/c_fit,h_172,w_226/v1421732950/http_www.asms.ru_bitrix_templates_main_images_nophoto_irnofq.png" } ; + return ( + React.createElement("tr", {key: i.get('id'), className: "cart-table-row"}, + React.createElement("td", {className: "text-center"}, + React.createElement("img", {style: {maxWidth: '90px', maxHeight: '90px'}, src: firstImage.url, alt: "product image"}) + ), + React.createElement("td", null, + React.createElement("p", null, " ", i.get('brand').name), + React.createElement("p", null, + i.get('name') + ) + ), + React.createElement("td", null, Globals.FormatCurrency(price) ), + React.createElement("td", null, + React.createElement("select", {style: {textAlign: 'center'}, value: count, className: "form-control", + onChange: self._onQuantityChange.bind(self, i.get('id')) + }, + + React.createElement("option", {value: "1"}, "1"), + React.createElement("option", {value: "2"}, "2"), + React.createElement("option", {value: "3"}, "3"), + React.createElement("option", {value: "4"}, "4"), + React.createElement("option", {value: "5"}, "5"), + React.createElement("option", {value: "6"}, "6"), + React.createElement("option", {value: "7"}, "7"), + React.createElement("option", {value: "8"}, "8"), + React.createElement("option", {value: "9"}, "9"), + React.createElement("option", {value: "10"}, "10") + ) + ), + React.createElement("td", null, + Globals.FormatCurrency(count * price) + ), + React.createElement("td", null, + React.createElement("button", {className: "btn btn-default", onClick: self._onTakeItemOut.bind(self, i.get('id'))}, "Ukloni iz korpe") + ) + )) + }); + + var deliveryDestination = (React.createElement("span", null)); + + if (this.state.destinationValid) { + deliveryDestination = ( + React.createElement("div", null, + "Na adresu ", this.state.deliveryDestination.name, "," + ) + ) + } + + var cartTotal = ( + React.createElement("div", null, + React.createElement("div", {className: "row cart-total"}, + React.createElement("div", {className: "col-lg-6"}, "Ukupno"), + React.createElement("div", {className: "col-lg-6"}, + React.createElement(CartTotal, {items: this.state.items, itemCounts: this.state.itemCounts, deliveryCosts: this.state.deliveryCosts}) + ) + ), + React.createElement("div", {className: "row"}, + React.createElement("div", {className: "col-lg-12 pull-right"}, + React.createElement("button", {className: "mybutton", onClick: this._onOrderClick}, "Završi narudžbu") + ) + + ) + ) + ); + + var buySomethingMessage = (React.createElement("div", null)); + var content; + + if (displayedItems.length <= 0) { + cartTotal = (React.createElement("div", null)) + buySomethingMessage = (React.createElement("div", null, + React.createElement("div", {className: "text-primary"}, "Nemate ni jedan artikal u vašoj korpi. Kada vidite nešto što vam se sviđa - pritisnite dugme UBACI U KORPU pored artikla kako biste ga dodali u korpu."), + React.createElement("div", null, "Evo nekoliko artikala koje vam možemo preporučiti: "), + React.createElement(AllItemsInGroup, {groupId: Globals.ItemGroupIdOfEmptyCartPage}) + + )) + content = buySomethingMessage; + } else { + content = (React.createElement("div", null, + React.createElement("table", {className: "table"}, + React.createElement("thead", null, + React.createElement("tr", null, + React.createElement("th", {className: "col-lg-2"}), + React.createElement("th", null, "Proizvod"), + React.createElement("th", null, "Cijena"), + React.createElement("th", {className: "col-lg-1"}, "Količina"), + React.createElement("th", null, "Ukupna cijena"), + React.createElement("th", null) + ) + ), + React.createElement("tbody", null, + displayedItems + ) + ), + buySomethingMessage, + cartTotal + )) + } + + return ( + + React.createElement("div", {className: "col-lg-12"}, + React.createElement("div", {className: "row"}, + React.createElement("div", {className: "col-lg-12"}, + React.createElement(LinkBanner, {locationName: "checkoutPage"}) + ) + ), + React.createElement("div", {className: "row"}, + React.createElement("div", {className: "col-lg-12"}, + React.createElement("div", {className: "cart-title"}, "KORPA"), + content + ) + ) + ) + ); + + }, + + // Add change listeners to stores + componentDidMount: function() { + CartStore.addChangeListener(this._onChange); + CartActions.load(); + }, + + componentWillUnmount: function () { + CartStore.removeChangeListener(this._onChange); + }, + + _onChange: function () { + if (this.isMounted()) { + this.setState(CartStore.getWholeCartState()); + } + + }, + _onOrderClick: function () { + NavigationActions.goToCheckout(); + }, + _onQuantityChange(itemId, e) { + CartActions.setItemCount(itemId, e.target.value); + }, + getInitialState: function () { + return CartStore.getWholeCartState(); + } +}); + + +module.exports = CartPage; + + +},{"../../actions/cartActions":"/home/senadu/projects/ribica/front-ui/app/actions/cartActions.js","../../actions/navigationActions":"/home/senadu/projects/ribica/front-ui/app/actions/navigationActions.js","../../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","../../stores/cartStore":"/home/senadu/projects/ribica/front-ui/app/stores/cartStore.js","../cart/addToCart":"/home/senadu/projects/ribica/front-ui/app/components/cart/addToCart.js","../items/allItemsInGroup":"/home/senadu/projects/ribica/front-ui/app/components/items/allItemsInGroup.js","../items/singleItem":"/home/senadu/projects/ribica/front-ui/app/components/items/singleItem.js","../linkBanner/linkBanner":"/home/senadu/projects/ribica/front-ui/app/components/linkBanner/linkBanner.js","./cartTotal":"/home/senadu/projects/ribica/front-ui/app/components/cart/cartTotal.js","react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js","react-router":"/home/senadu/projects/ribica/front-ui/node_modules/react-router/modules/index.js"}],"/home/senadu/projects/ribica/front-ui/app/components/cart/cartTotal.js":[function(require,module,exports){ +var React = require('react'), + Globals = require('../../globals'); + ; + +var Router = require('react-router'); + + + +var CartTotal = React.createClass({displayName: "CartTotal", + + render: function() { + + + + var counts = this.props.itemCounts; + var total = 0; + + var items = this.props.items.models; + + for (var i = 0; i < items.length; i++) { + var item = items[i]; + var count = counts[item.get('id')].get('count'); + var price = item.get('list_price'); + total += (price * count) + }; + + + + return ( React.createElement("span", null, Globals.FormatCurrency(total))); + + } + +}); + + +module.exports = CartTotal; + + +},{"../../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js","react-router":"/home/senadu/projects/ribica/front-ui/node_modules/react-router/modules/index.js"}],"/home/senadu/projects/ribica/front-ui/app/components/cart/checkoutPage.js":[function(require,module,exports){ +var React = require('react'), + CartStore = require('../../stores/cartStore'), + AddToCart = require('../cart/addToCart'), + CartActions = require('../../actions/cartActions'), + NavigationActions = require('../../actions/navigationActions'), + SingleItem = require('../items/singleItem'), + Globals = require('../../globals'), + CartTotal = require('./cartTotal'), + LinkBanner = require('../linkBanner/linkBanner'), + RibicaFormError = require('../shared/ribicaFormError'); + + +var Router = require('react-router'); + + +var CheckoutPage = React.createClass({displayName: "CheckoutPage", + + render: function() { + + var supportedPlaceOptions = CartStore.getSupportedPlaces().map ( function (p) { return (React.createElement("option", {value: p.code}, p.placeLabel))}); + + var content = ( +React.createElement("div", {className: "checkout-page center"}, + React.createElement("div", {className: "form-horizontal"}, + React.createElement("fieldset", null, + React.createElement("legend", null, "Dostava"), + React.createElement("div", {className: "form-group"}, + + React.createElement("label", {className: "col-md-4 control-label", htmlFor: "name"}, "Prezime i Ime"), + React.createElement("div", {className: "col-md-4"}, + React.createElement(RibicaFormError, {componentName: "name", errorMessagesObject: this.state.deliveryDestinationErrors}), + React.createElement("input", {id: "name", name: "name", type: "text", placeholder: "Prezime Ime", className: "form-control input-md", required: "", value: this.state.deliveryDestination.get('name'), onChange: this._onFieldChange}), + React.createElement("span", {className: "help-block"}, "ime osobe koja prima pošiljku") + ) + ), + React.createElement("div", {className: "form-group"}, + React.createElement("label", {className: "col-md-4 control-label", htmlFor: "name"}, "Adresa"), + React.createElement("div", {className: "col-md-4"}, + React.createElement(RibicaFormError, {componentName: "address", errorMessagesObject: this.state.deliveryDestinationErrors}), + React.createElement("input", {id: "address", name: "address", type: "text", placeholder: "Ulica i broj", className: "form-control input-md", required: "", value: this.state.deliveryDestination.get('address'), onChange: this._onFieldChange}), + React.createElement("span", {className: "help-block"}, "adresa na koju će roba biti isporučena") + ) + ), + + React.createElement("div", {className: "form-group"}, + React.createElement("label", {className: "col-md-4 control-label", htmlFor: "place"}, "Mjesto"), + React.createElement("div", {className: "col-md-4"}, + React.createElement(RibicaFormError, {componentName: "place", errorMessagesObject: this.state.deliveryDestinationErrors}), + React.createElement("select", {id: "place", name: "place", className: "form-control", value: this.state.deliveryDestination.get('place'), onChange: this._onFieldChange}, + + supportedPlaceOptions + + ) + ) + ), + React.createElement("div", {className: "form-group"}, + React.createElement("label", {className: "col-md-4 control-label", htmlFor: "phone"}, "Telefon"), + React.createElement("div", {className: "col-md-4"}, + React.createElement(RibicaFormError, {componentName: "phone", errorMessagesObject: this.state.deliveryDestinationErrors}), + React.createElement("div", {className: "input-group"}, + React.createElement("span", {className: "input-group-addon"}, "+387 "), + React.createElement("input", {id: "phone", name: "phone", className: "form-control", placeholder: "061 222 333", type: "text", required: "", value: this.state.deliveryDestination.get('phone'), onChange: this._onFieldChange}) + ), + React.createElement("p", {className: "help-block"}, "broj mobitela - mora biti sa jedne od mreža u BiH") + ) + ), + React.createElement("div", {className: "form-group"}, + React.createElement("label", {className: "col-md-4 control-label", htmlFor: "email"}, "E - mail"), + React.createElement("div", {className: "col-md-4"}, + React.createElement(RibicaFormError, {componentName: "email", errorMessagesObject: this.state.deliveryDestinationErrors}), + React.createElement("input", {id: "email", name: "email", type: "text", placeholder: "ime@nekimail.com", className: "form-control input-md", required: "", value: this.state.deliveryDestination.get('email'), onChange: this._onFieldChange}), + React.createElement("span", {className: "help-block"}, "E - mail adresa na koju će vam biti poslano obavještenje o narudžbi") + ) + ), + React.createElement("div", {className: "form-group"}, + React.createElement("label", {className: "col-md-4 control-label", htmlFor: "note"}, "Napomena"), + React.createElement("div", {className: "col-md-4"}, + React.createElement("textarea", {className: "form-control", id: "note", name: "note", value: this.state.deliveryDestination.get('note'), onChange: this._onFieldChange}) + ) + ), + React.createElement("div", {className: "form-group"}, + React.createElement("label", {className: "col-md-4 control-label", htmlFor: "order"}), + React.createElement("div", {className: "col-md-8"}, + React.createElement("div", null, "Ukupno: ", React.createElement(CartTotal, {items: this.state.items, itemCounts: this.state.itemCounts, deliveryCosts: this.state.deliveryCosts}), " "), + React.createElement("div", null, React.createElement("button", {id: "order", name: "order", className: "mybutton", disabled: !this.state.isDeliveryDestinationValid, onClick: this._onOrderClick}, "Završi narudžbu")) + ) + ) + ) +) +) + ); + + if(CartStore.isAddressColapsed()) { + + var address = CartStore.getHumanReadableAddress().map(function (a) { return (React.createElement("span", null, a, React.createElement("br", null)))}); + content = ( + React.createElement("div", {className: "checkout-page center text-center"}, + React.createElement("h2", null, " Roba će biti dostavljena na adresu: "), + React.createElement("p", {className: "lead"}, + address, + React.createElement("br", null), + "Ukupno: ", React.createElement(CartTotal, {items: this.state.items, itemCounts: this.state.itemCounts, deliveryCosts: this.state.deliveryCosts}) + + ), + React.createElement("p", null, + React.createElement("button", {id: "order", name: "order", className: "mybutton", disabled: !this.state.isDeliveryDestinationValid, onClick: this._onOrderClick}, "Završi narudžbu"), " ili ", React.createElement("button", {className: "btn btn-default", onClick: this._onUncolapseClick}, "Promijeni adresu") + ), + React.createElement("div", {className: "form-group"}, + React.createElement("label", {className: "col-md-4 control-label", htmlFor: "order"}), + React.createElement("div", {className: "col-md-8"}, + React.createElement("div", null, " "), + React.createElement("div", null) + ) + ) + )); + } + + + return content; + + }, + + // Add change listeners to stores + componentDidMount: function() { + CartStore.addChangeListener(this._onChange); + CartActions.load(); + }, + + componentWillUnmount: function () { + CartStore.removeChangeListener(this._onChange); + }, + + _onChange: function () { + if (this.isMounted()) { + this.setState(CartStore.getWholeCartState()); + } + + }, + _onFieldChange: function (event) { + CartActions.changeDeliveryDestinationProperty(event.target.name, event.target.value); + }, + + _onOrderClick: function (event) { + CartActions.confirmDelivery(); + }, + + _onUncolapseClick: function (event) { + CartActions.setAddressColapsed(false); + }, + + getInitialState: function () { + return CartStore.getWholeCartState(); + } + +}); + + +module.exports = CheckoutPage; + + +},{"../../actions/cartActions":"/home/senadu/projects/ribica/front-ui/app/actions/cartActions.js","../../actions/navigationActions":"/home/senadu/projects/ribica/front-ui/app/actions/navigationActions.js","../../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","../../stores/cartStore":"/home/senadu/projects/ribica/front-ui/app/stores/cartStore.js","../cart/addToCart":"/home/senadu/projects/ribica/front-ui/app/components/cart/addToCart.js","../items/singleItem":"/home/senadu/projects/ribica/front-ui/app/components/items/singleItem.js","../linkBanner/linkBanner":"/home/senadu/projects/ribica/front-ui/app/components/linkBanner/linkBanner.js","../shared/ribicaFormError":"/home/senadu/projects/ribica/front-ui/app/components/shared/ribicaFormError.js","./cartTotal":"/home/senadu/projects/ribica/front-ui/app/components/cart/cartTotal.js","react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js","react-router":"/home/senadu/projects/ribica/front-ui/node_modules/react-router/modules/index.js"}],"/home/senadu/projects/ribica/front-ui/app/components/items/allItems.js":[function(require,module,exports){ +var React = require('react'); +var ItemList = require('./itemList'); +var ItemStore = require('../../stores/itemStore.js'); +var ItemActions = require('../../actions/itemActions.js'); +var ItemCollection = require('../../models/itemCollection'); + +var AllItems = React.createClass({displayName: "AllItems", + + render: function() { + return ( + React.createElement(ItemList, {items: this.state.items}) + ); + }, + + // Add change listeners to stores + componentDidMount: function() { + ItemActions.loadFrontPageItems(); + ItemStore.addChangeListener(this._onChange); + }, + + + getInitialState: function() { + return { + items: ItemStore.getItems() + } + }, + + + _onChange: function () { + if (this.isMounted()) { + this.setState({ + items: ItemStore.getItems() + }); + } + }, +}); + +module.exports = AllItems; + + +},{"../../actions/itemActions.js":"/home/senadu/projects/ribica/front-ui/app/actions/itemActions.js","../../models/itemCollection":"/home/senadu/projects/ribica/front-ui/app/models/itemCollection.js","../../stores/itemStore.js":"/home/senadu/projects/ribica/front-ui/app/stores/itemStore.js","./itemList":"/home/senadu/projects/ribica/front-ui/app/components/items/itemList.js","react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js"}],"/home/senadu/projects/ribica/front-ui/app/components/items/allItemsInGroup.js":[function(require,module,exports){ +var React = require('react'); +var ItemList = require('./itemList'); +var ItemStore = require('../../stores/itemStore.js'); +var ItemActions = require('../../actions/itemActions.js'); +var ItemCollection = require('../../models/itemCollection'); +var NavigationStore = require('../../stores/navigationStore.js'); + +var AllItemsInGroup = React.createClass({displayName: "AllItemsInGroup", + + render: function() { + return ( + React.createElement(ItemList, {items: this.state.items}) + ); + }, + + // Add change listeners to stores + componentDidMount: function() { + var groupId = this.props.groupId || NavigationStore.getGroupIdFromUrl(); + ItemActions.loadBestSellingItemsForGroup(groupId); + ItemStore.addChangeListener(this._onChange); + }, + + + componentWillUnmount: function () { + ItemStore.removeChangeListener(this._onChange); + }, + + + getInitialState: function() { + return { + items: ItemStore.getItemsForGroup() + } + }, + + + _onChange: function () { + if (this.isMounted()) { + this.setState({ + items: ItemStore.getItemsForGroup() + }); + } + }, +}); + +module.exports = AllItemsInGroup; + + +},{"../../actions/itemActions.js":"/home/senadu/projects/ribica/front-ui/app/actions/itemActions.js","../../models/itemCollection":"/home/senadu/projects/ribica/front-ui/app/models/itemCollection.js","../../stores/itemStore.js":"/home/senadu/projects/ribica/front-ui/app/stores/itemStore.js","../../stores/navigationStore.js":"/home/senadu/projects/ribica/front-ui/app/stores/navigationStore.js","./itemList":"/home/senadu/projects/ribica/front-ui/app/components/items/itemList.js","react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js"}],"/home/senadu/projects/ribica/front-ui/app/components/items/itemGroupPage.js":[function(require,module,exports){ +var React = require('react'), + Router = require('react-router'), + RouteHandler = Router.RouteHandler, + AllItemsInGroup = require('../items/allItemsInGroup'); + +var ItemGroupPage = React.createClass({displayName: "ItemGroupPage", + render : function() { + return ( + React.createElement("div", null, + + React.createElement("div", {className: "col-md-2"} + + ), + React.createElement("div", {className: "col-md-10"}, + React.createElement(AllItemsInGroup, null) + ) + ) + ) + } +}); + +module.exports = ItemGroupPage; + + +},{"../items/allItemsInGroup":"/home/senadu/projects/ribica/front-ui/app/components/items/allItemsInGroup.js","react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js","react-router":"/home/senadu/projects/ribica/front-ui/node_modules/react-router/modules/index.js"}],"/home/senadu/projects/ribica/front-ui/app/components/items/itemList.js":[function(require,module,exports){ +var React = require('react'); +var SingleItem = require('./singleItem'); +var ItemCollection = require('../../models/itemCollection.js'); + +var ItemList = React.createClass({displayName: "ItemList", + changePage: function(page, e) { + e.preventDefault(); + if(this.props.onPageChange) { + this.props.onPageChange(page); + } + }, + render: function() { + + var items = this.props.items.models.map( function(item) { + return ( + React.createElement(SingleItem, {item: item, key: item.id}) + ); + }); + + return ( + React.createElement("div", {className: "row-fluid"}, + React.createElement("div", {className: "span10"}, + React.createElement("div", {style: {marginTop: 35, padding: '0 25px'}, className: "row"}, + items + ), + this.getPages() + ) + ) + ); + }, + getPages: function() { + if (!this.props.paginationEnabled) { + return ""; + } + + var nrOfPages = Math.ceil(this.props.total/ this.props.limit); + if (nrOfPages === 1) { + return ""; + } + + var maxSlots = 10; + var selectedIndex = Math.floor(this.props.currentOffset / this.props.limit); + + var start, end; + start = selectedIndex - Math.floor(maxSlots / 2); + end = selectedIndex + Math.floor(maxSlots / 2 ) + 1; + + if (start < 0) start = 0; + if (end > nrOfPages) end = nrOfPages; + + if ((end - start) < maxSlots) { + start = Math.max(0, end - maxSlots); + end = Math.min(nrOfPages, start + maxSlots); + } + + var pages = []; + for(var i = start; i < end; i++) { + var cn = i === selectedIndex ? "active": ""; + pages.push(React.createElement("li", {className: cn}, React.createElement("a", {onClick: this.changePage.bind(this, i), href: "#"}, i + 1))) + } + + return ( + React.createElement("nav", null, + + React.createElement("ul", {className: "pagination"}, + pages + ) + )) + } + +}); + + +module.exports = ItemList; + + +},{"../../models/itemCollection.js":"/home/senadu/projects/ribica/front-ui/app/models/itemCollection.js","./singleItem":"/home/senadu/projects/ribica/front-ui/app/components/items/singleItem.js","react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js"}],"/home/senadu/projects/ribica/front-ui/app/components/items/itemWithDetailsPage.js":[function(require,module,exports){ +var React = require('react'), + Carousel = require('../shared/carousel'), + Traits = require('../items/traits'), + ItemDetailsActions = require('../../actions/itemDetailsActions'), + NavigationStore = require('../../stores/navigationStore'), + ItemDetailsStore = require('../../stores/itemDetailsStore'), + AddToCart = require('../cart/addToCart'); + +var Router = require('react-router'); +var Globals = require('../../globals'); + + +var ItemWithDetailsPage = React.createClass({displayName: "ItemWithDetailsPage", + + render: function() { + + return ( + + React.createElement("div", {className: "item-with-details row-fluid center"}, + React.createElement("div", {className: "col-md-5 col-md-offset-2 item_image"}, + React.createElement("img", {src: this.state.firstImage, width: "100%"}) + ), + + React.createElement("div", {className: "col-md-5"}, + React.createElement("div", {className: "item_brand_name"}, " ", this.state.item.get('brand').name), + React.createElement("div", {className: "item_name"}, " ", this.state.item.get('name')), + React.createElement("div", null, + React.createElement("div", {className: "item_price"}, " ", Globals.FormatCurrency(this.state.item.get('list_price'))), + + React.createElement("div", null, "Količina"), + + React.createElement("div", null, " ", React.createElement(AddToCart, {item: this.state.item})), + + React.createElement("div", null, + React.createElement("div", {className: "item_description_tab"}, "Opis proizvoda"), + React.createElement("div", {className: "item_description_tab_area"}, " "), + React.createElement("div", {className: "item_description_text"}, this.state.item.get('description')) + ) + ), + + React.createElement(Traits, {traits: this.state.item.get('traits')}) + + ) + ) + ); + + }, + + // Add change listeners to stores + componentDidMount: function() { + ItemDetailsStore.addChangeListener(this._onChange); + NavigationStore.addChangeListener(this._onChange); + ItemDetailsActions.loadItemWithDetails(); + }, + + componentWillUnmount: function () { + ItemDetailsStore.removeChangeListener(this._onChange); + NavigationStore.removeChangeListener(this._onChange); + }, + + + onClickLeft: function() { + ItemDetailsActions.previousCarouselImage(); + + }, + + onClickRight: function() { + ItemDetailsActions.nextCarouselImage(); + }, + + onSelectImage: function(i) { + ItemDetailsActions.selectCarouselImage(i); + }, + + _onChange: function () { + + if (this.isMounted()) { + this.setState(ItemDetailsStore.getState()); + } + + }, + + getInitialState: function () { + return ItemDetailsStore.getState(); + } + +}); + + +module.exports = ItemWithDetailsPage; + + +},{"../../actions/itemDetailsActions":"/home/senadu/projects/ribica/front-ui/app/actions/itemDetailsActions.js","../../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","../../stores/itemDetailsStore":"/home/senadu/projects/ribica/front-ui/app/stores/itemDetailsStore.js","../../stores/navigationStore":"/home/senadu/projects/ribica/front-ui/app/stores/navigationStore.js","../cart/addToCart":"/home/senadu/projects/ribica/front-ui/app/components/cart/addToCart.js","../items/traits":"/home/senadu/projects/ribica/front-ui/app/components/items/traits.js","../shared/carousel":"/home/senadu/projects/ribica/front-ui/app/components/shared/carousel.js","react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js","react-router":"/home/senadu/projects/ribica/front-ui/node_modules/react-router/modules/index.js"}],"/home/senadu/projects/ribica/front-ui/app/components/items/singleItem.js":[function(require,module,exports){ +var React = require('react'); +var ItemActions = require('../../actions/itemActions'); +var NavigationActions = require('../../actions/navigationActions'); +var NavigationStore = require('../../stores/navigationStore'); + +var Globals = require('../../globals'); +var Router = require('react-router'); + +var SingleItem = React.createClass({displayName: "SingleItem", + render: function() { + var hidePrice = this.props.hidePrice || false; + var self = this; + var itemClick = this.itemClick; + var firstImage = this.props.item.get('multi_media_descriptions')[0]; + firstImage = firstImage || { resized_url: "https://res.cloudinary.com/lfvt7ps2n/image/upload/c_fit,h_172,w_226/v1421732950/http_www.asms.ru_bitrix_templates_main_images_nophoto_irnofq.png" } ; + if (hidePrice) { + return ( + React.createElement("div", {className: "col-lg-2 col-md-2 col-sm-3 col-xs-6"}, + React.createElement("div", {className: "productbox"}, + React.createElement("img", {className: "img-responsive", src: firstImage.url, alt: "product image"}), + React.createElement("div", null, + + React.createElement("p", null, " ", React.createElement("p", {className: "productbox item_brand_name"}, this.props.item.get('brand') ? this.props.item.get('brand').name : ''), this.props.item.get('name') ) + + ) + ) + ) + ); + } + else { + return ( + + React.createElement("div", {className: "col-lg-3 col-md-3 col-sm-4 col-xs-6", onClick: itemClick}, + React.createElement("div", {className: "productbox"}, + React.createElement("img", {className: "img-responsive", src: firstImage.resized_url, alt: "product image"}), + React.createElement("div", {style: {height: "90px"}, className: "item_name_and_price"}, + React.createElement("p", null, React.createElement("span", {className: "text-uppercase"}, this.props.item.get('brand') ? this.props.item.get('brand').name : ''), + React.createElement("br", null), React.createElement("span", {className: "text-capitalize"}, this.props.item.get('name') )), + React.createElement("h4", {className: "item_floating_price"}, Globals.FormatCurrency(this.props.item.get('list_price')) ) + ) + ) + ) + ); + } + }, + + itemClick: function(e) { + NavigationActions.goToItemDetails(this.props.item); + + } +}); + + +module.exports = SingleItem; + + +},{"../../actions/itemActions":"/home/senadu/projects/ribica/front-ui/app/actions/itemActions.js","../../actions/navigationActions":"/home/senadu/projects/ribica/front-ui/app/actions/navigationActions.js","../../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","../../stores/navigationStore":"/home/senadu/projects/ribica/front-ui/app/stores/navigationStore.js","react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js","react-router":"/home/senadu/projects/ribica/front-ui/node_modules/react-router/modules/index.js"}],"/home/senadu/projects/ribica/front-ui/app/components/items/traits.js":[function(require,module,exports){ +var React = require('react'); + +var Traits = React.createClass({displayName: "Traits", + render: function() { + + var traitsPresentation = []; + var traits = (this.props.traits || {}); + for(var traitKey in traits ) { + if(traits.hasOwnProperty(traitKey)) { + var traitValue = this.props.traits[traitKey]; + traitsPresentation.push( React.createElement("div", {key: traitKey, className: "single_trait"}, traitKey, ": ", traitValue) ); + } + } + + + return ( + React.createElement("div", {className: "row-fluid"}, + React.createElement("div", {className: "span12"}, + traitsPresentation + ) + ) + ); + } +}); + + +module.exports = Traits; + + +},{"react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js"}],"/home/senadu/projects/ribica/front-ui/app/components/linkBanner/linkBanner.js":[function(require,module,exports){ +var React = require('react'); +var ItemActions = require('../../actions/itemActions'); +var NavigationActions = require('../../actions/navigationActions'); +var InitializationStore = require('../../stores/initializationStore') + +var Router = require('react-router'); + +var LinkBanner = React.createClass({displayName: "LinkBanner", + propTypes: { + locationName: React.PropTypes.string.isRequired, + locationId: React.PropTypes.number, + }, + + + render: function() { + + var banners = this.state.banners.map(function (banner) { + + return React.createElement("div", {key: "banner" + banner.get('id')}, React.createElement("a", {href: banner.get('link_url')}, React.createElement("img", {className: "img-responsive center-block", src: banner.get('image_url')}))) + + }); + + return (React.createElement("div", null, banners )); + + }, + getInitialState: function () { + + + var allBanners = InitializationStore.getBanners(); + var locationId = this.props.locationId; + var locationName = this.props.locationName; + var bannersToShow = []; + + if (locationId) { + bannersToShow = allBanners[locationName][locationId]; + + } else { + bannersToShow = allBanners[locationName]; + + } + + bannersToShow = bannersToShow || []; + + + return { banners: bannersToShow }; + } + +}); + + +module.exports = LinkBanner; + + +},{"../../actions/itemActions":"/home/senadu/projects/ribica/front-ui/app/actions/itemActions.js","../../actions/navigationActions":"/home/senadu/projects/ribica/front-ui/app/actions/navigationActions.js","../../stores/initializationStore":"/home/senadu/projects/ribica/front-ui/app/stores/initializationStore.js","react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js","react-router":"/home/senadu/projects/ribica/front-ui/node_modules/react-router/modules/index.js"}],"/home/senadu/projects/ribica/front-ui/app/components/rootApp.js":[function(require,module,exports){ +var React = require('react'), + MenuItemListComponent = require('./shared/menuItemListComponent'), + SectionListComponent = require('./shared/sectionsListComponent'), + + Router = require('react-router'), + Link = Router.Link, + RouteHandler = Router.RouteHandler, + LoginStatus = require('./shared/loginStatus'), + InitializationStore = require('../stores/initializationStore'), + NavigationStore = require('../stores/navigationStore'), + InitializationActions = require('../actions/initializationActions'); + +var CartIcon = require('./cart/cartIcon'); +var SearchBox = require('./shared/searchBox'); + +var RootApp = React.createClass({displayName: "RootApp", + + // Add change listeners to stores + componentDidMount: function() { + InitializationStore.addChangeListener(this._onChange); + InitializationActions.initialize(); + }, + + + getInitialState: function() { + return InitializationStore.getState(); + }, + + + _onChange: function () { + if (this.isMounted()) { + this.setState(InitializationStore.getState()); + } + }, + + componentWillUnmount: function () { + InitializationStore.removeChangeListener(this._onChange); + }, + + shouldShowCart: function () { + return !NavigationStore.hideCart(); + }, + + render: function() { + + if (!this.state.isEverythingReadyToStartTheShow) { + return (React.createElement("div", null, "loading...")); + } + + var cart = ""; + + if(this.shouldShowCart()) { + cart = (React.createElement(CartIcon, null)); + } + + return ( +React.createElement("div", {className: "container"}, + React.createElement("div", null, + React.createElement("div", {className: "col-lg-12 hidden-sm hidden-xs ", style: {height: 80, background: 'none', marginBottom: '0px !important'}, id: "mybody"}, + + React.createElement("div", {style: {padding: '15px 15px'}, className: "col-lg-2 col-md-2 col-sm-2 col-xs-2"}, + React.createElement(Link, {to: "app"}, React.createElement("img", {height: 50, src: "https://res.cloudinary.com/du5pdibul/image/upload/v1428813560/logo_h5f9yp.png"})) + ), + + React.createElement("div", {style: {padding: '30px 15px'}, className: "col-lg-6 col-md-6 col-sm-6 col-xs-6"}, + React.createElement(SearchBox, null) + ), + + React.createElement("div", {style: {padding: '15px 15px'}, className: "col-lg-4 col-md-4 hidden-sm hidden-xs"}, + React.createElement("nav", {style: {background: 'none', border: 'none', marginBottom: '0px !important'}, className: "navbar mytopnav"}, + React.createElement("div", null, + cart + ) + ) + ) + ), + React.createElement("div", {style: {margin: '0 !important'}, className: "clearfix"} + ) + ), + React.createElement("div", null, + React.createElement(MenuItemListComponent, null) + ), + + React.createElement("div", {className: "row"}, + React.createElement(RouteHandler, null) + ) +) +); + + + + } +}); + +module.exports = RootApp; + + +},{"../actions/initializationActions":"/home/senadu/projects/ribica/front-ui/app/actions/initializationActions.js","../stores/initializationStore":"/home/senadu/projects/ribica/front-ui/app/stores/initializationStore.js","../stores/navigationStore":"/home/senadu/projects/ribica/front-ui/app/stores/navigationStore.js","./cart/cartIcon":"/home/senadu/projects/ribica/front-ui/app/components/cart/cartIcon.js","./shared/loginStatus":"/home/senadu/projects/ribica/front-ui/app/components/shared/loginStatus.js","./shared/menuItemListComponent":"/home/senadu/projects/ribica/front-ui/app/components/shared/menuItemListComponent.js","./shared/searchBox":"/home/senadu/projects/ribica/front-ui/app/components/shared/searchBox.js","./shared/sectionsListComponent":"/home/senadu/projects/ribica/front-ui/app/components/shared/sectionsListComponent.js","react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js","react-router":"/home/senadu/projects/ribica/front-ui/node_modules/react-router/modules/index.js"}],"/home/senadu/projects/ribica/front-ui/app/components/search/searchResultsPage.js":[function(require,module,exports){ +var React = require('react'), + NavigationActions = require('../../actions/navigationActions'), + Globals = require('../../globals') + Router = require("react-router"), + LinkBanner = require('../linkBanner/linkBanner'), + Link = Router.Link; + +var SearchStore = require('../../stores/searchStore'); +var SearchActions = require('../../actions/searchActions'); + +var ItemList = require('../items/itemList'); +var SearchResultsPage = React.createClass({displayName: "SearchResultsPage", + mixins: [Router.State], + getInitialState: function() { + return SearchStore.getSearchResultsState(); + }, + render: function() { + var content; + + if (this.state.items.length > 0) { + content = React.createElement(ItemList, {items: this.state.items}) + } else { + + content = React.createElement("div", null, "Nema rezultata za vašu pretragu.") + } + return ( + React.createElement("div", null, + + React.createElement(LinkBanner, {locationName: "searchResultPage"}), + React.createElement("h2", null, "Rezultati pretrage za '", this.state.q, "'"), + + content + ) + ); + }, + componentWillReceiveProps: function() { + this.update(); + }, + update: function(){ + var query = this.getQuery(); + SearchActions.getSearchResults(query.q); + }, + componentDidMount: function() { + SearchStore.addChangeListener(this._onChange); + this.update(); + + //CartActions.load(); + }, + componentWillUnmount: function () { + SearchStore.removeChangeListener(this._onChange); + }, + _onChange: function() { + if(this.isMounted()) { + this.setState(SearchStore.getSearchResultsState()); + } + } +}); + + +module.exports = SearchResultsPage; + + +},{"../../actions/navigationActions":"/home/senadu/projects/ribica/front-ui/app/actions/navigationActions.js","../../actions/searchActions":"/home/senadu/projects/ribica/front-ui/app/actions/searchActions.js","../../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","../../stores/searchStore":"/home/senadu/projects/ribica/front-ui/app/stores/searchStore.js","../items/itemList":"/home/senadu/projects/ribica/front-ui/app/components/items/itemList.js","../linkBanner/linkBanner":"/home/senadu/projects/ribica/front-ui/app/components/linkBanner/linkBanner.js","react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js","react-router":"/home/senadu/projects/ribica/front-ui/node_modules/react-router/modules/index.js"}],"/home/senadu/projects/ribica/front-ui/app/components/shared/carousel.js":[function(require,module,exports){ +var React = require("react"); + +var Carousel = React.createClass({displayName: "Carousel", + 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 ( + React.createElement("div", null, + React.createElement("span", {className: "arrow left", + onClick: this.props.onClickLeft}, "◄"), + React.createElement("div", {className: "carousel-stage"}, + React.createElement("ul", {style: ulStyle, className: "carousel-list"}, + this.props.images.map(function(image, i) { + return React.createElement("li", {key: i}, React.createElement("img", {src: image})); + }) + ), + React.createElement("ul", {className: "dots"}, + this.props.images.map(function(image, i) { + var activeClass = i === this.props.selected ? "active" : ""; + return React.createElement("li", {key: i, + className: "circle " + activeClass, + onClick: this.onClickDot.bind(this, i)}); + }.bind(this)) + ) + ), + React.createElement("span", {className: "arrow right", + onClick: this.props.onClickRight}, "►") + ) + ) + }, + + onClickDot: function(index) { + this.props.onSelectImage(index); + } +}); + +module.exports = Carousel; + +},{"react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js"}],"/home/senadu/projects/ribica/front-ui/app/components/shared/loginStatus.js":[function(require,module,exports){ +var React = require("react"), + Router = require("react-router"), + Link = Router.Link; + +var UserStore = require('../../stores/userStore'); +var UserActions = require('../../actions/userActions'); + +var LoginStatus = React.createClass({displayName: "LoginStatus", + getInitialState: function() { + return UserStore.getLoginState(); + }, + componentDidMount: function() { + UserStore.addChangeListener(this.onUserStateChange); + UserActions.checkLogin(); + }, + componentWillReceiveProps: function() { + this.update(); + }, + componentWillUnmount: function() { + UserStore.removeChangeListener(this.onUserStateChange); + }, + onUserStateChange: function() { + this.update(); + }, + update: function() { + if(this.isMounted()) { + this.setState(UserStore.getLoginState()); + } + }, + logout: function(e){ + e.preventDefault(); + UserActions.userLogout(); + }, + render : function() { + + + var content; + + if(this.state.loggedIn){ + content = (React.createElement("div", {style: {display: 'inline-block', paddingTop: '18px', paddingRight: '10px'}}, this.state.user.first_name, " ", this.state.user.last_name, " ", React.createElement("a", {onClick: this.logout, style: { paddingLeft: '10px'}}, "Odjavite se"))) + } else { + content = (React.createElement("div", {style: {display: 'inline-block', paddingTop: '18px', paddingRight: '10px'}}, React.createElement(Link, {to: "registracija", style: { paddingRight: '10px'}}, "Registracija"), " ", React.createElement(Link, {to: "login"}, "Prijava"))) + } + + return content; + } +}) +module.exports = LoginStatus; + + +},{"../../actions/userActions":"/home/senadu/projects/ribica/front-ui/app/actions/userActions.js","../../stores/userStore":"/home/senadu/projects/ribica/front-ui/app/stores/userStore.js","react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js","react-router":"/home/senadu/projects/ribica/front-ui/node_modules/react-router/modules/index.js"}],"/home/senadu/projects/ribica/front-ui/app/components/shared/menuItemListComponent.js":[function(require,module,exports){ +var React = require('react'), + MenuItemCollection = require('../../models/menuItemCollection'), + MenuItem = require('../../models/menuItem'), + Backbone = require('backbone'), + NavigationStore = require('../../stores/navigationStore'), + MenuItemStore = require('../../stores/menuItemStore'), + CartStore = require('../../stores/cartStore.js'); + MenuItemActions = require('../../actions/menuItemActions'), + NavigationActions = require('../../actions/navigationActions'); + +Backbone.$ = $; + +var MenuItemListComponent = React.createClass({displayName: "MenuItemListComponent", + + _onChange: function () { + if (this.isMounted()) { + this.setState(MenuItemStore.getState()); + } + }, + + getInitialState: function() { + var state = MenuItemStore.getState(); + var cartState = CartStore.getWholeCartState(); + state.cartCount = cartState; + return state; + }, + + componentDidMount: function() { + MenuItemStore.addChangeListener(this._onChange); + MenuItemActions.loadMenuItems(); + }, + onMouseOver: function(menuItem) { + MenuItemActions.setMenuItemHover(menuItem); + }, + onMouseOut: function() { + MenuItemActions.unsetMenuItemHover(); + }, + onMouseLeave: function() { + MenuItemActions.unsetMenuItemHover(); + }, + onMenuItemClick: function(menuItem, e) { + MenuItemActions.unsetMenuItemHover(); + NavigationActions.goToMenuItem(menuItem); + e.preventDefault(); + }, + _onCartClick: function(e) { + NavigationActions.goToCart(); + e.preventDefault(); + }, + + render: function() { + var self = this; + var style = { + position: 'relative' + }; + var abStyle = { + position: 'absolute' + }; + return ( + React.createElement("div", {className: "navbar navbar-default navbar-static-top"}, + React.createElement("div", {className: "container"}, + React.createElement("ul", {className: "nav nav-pills"}, + React.createElement("div", {className: "navbar-header"}, + React.createElement("button", {type: "button", className: "navbar-toggle", "data-toggle": "collapse", "data-target": ".navbar-collapse"}, + React.createElement("span", {className: "icon-bar"}), + React.createElement("span", {className: "icon-bar"}), + React.createElement("span", {className: "icon-bar"}) + ), + React.createElement("a", {className: "navbar-brand hidden-lg hidden-md", href: "#"}, React.createElement("img", {style: {marginTop: '-4px'}, height: 24, src: "https://res.cloudinary.com/du5pdibul/image/upload/v1428813560/logo_h5f9yp.png"})), + React.createElement("ul", {className: "mynav hidden-lg hidden-md ", style: {listStyle: 'none'}}, + React.createElement("li", null, React.createElement("a", {href: "#"}, "0,00 KM")), + React.createElement("li", {onClick: this._onCartClick}, React.createElement("a", {style: {marginLeft: 10}, className: "mybutton", href: "#", onClick: this._onCartClick}, "Završi narudžbu")) + ) + ), + React.createElement("div", {className: "navbar-collapse collapse"}, + React.createElement("ul", {className: "nav navbar-nav hidden-sm hidden-xs "}, + /* +
  • Današnja ponuda
  • +
  • + Beba + +
  • */ + + this.state.menuItems.map(function(menuItem) { + return React.createElement("li", {className: "mydropdown menu-large", onMouseLeave: self.onMouseOut, onMouseOver: self.onMouseOver.bind(self, menuItem)}, + React.createElement("a", {href: "#", className: "dropdown-toggle ", id: menuItem.get('title').toLowerCase(), onClick: self.onMenuItemClick.bind(self, menuItem)}, menuItem.get('title')), + React.createElement("ul", {className: menuItem.get('id') !== self.state.hoveredMenuItem ? "dropdown-menu megamenu row hide": "dropdown-menu megamenu row"}, + menuItem.get('menu_sub_items').map(function(menuSubItem) { + return ( + React.createElement("li", {className: "col-sm-3", key: menuSubItem.id}, + + React.createElement("ul", null, + React.createElement("li", {className: "dropdown-header"}, React.createElement("a", {href: "#", onClick: self.onMenuItemClick.bind(self, menuSubItem)}, React.createElement("p", null, menuSubItem.title))), + menuSubItem.menu_sub_sub_items.map(function(menuSubSubItem) { + return (React.createElement("li", null, React.createElement("a", {href: "#", onClick: self.onMenuItemClick.bind(self, menuSubSubItem)}, menuSubSubItem.title))) + }) + ) + ) + ) + }) + ) + ) + }) + ), + React.createElement("ul", {className: "nav navbar-nav hidden-lg hidden-md "}, + /* +
  • Današnja ponuda
  • +
  • + + +
  • +
  • Dijete
  • +
  • Mama
  • +
  • Made in BiH
  • +
  • Tržišna marka
  • + + */ + this.state.menuItems.map(function(menuItem) { + return React.createElement("li", {className: "dropdown menu-large"}, + React.createElement("a", {href: "#", className: "dropdown-toggle ", "data-toggle": "dropdown", role: "button", "aria-expanded": "false"}, + menuItem.get('title'), " ", React.createElement("b", {className: "caret"}), " "), + React.createElement("ul", {className: "dropdown-menu megamenu row"}, + + menuItem.get('menu_sub_items').map(function(menuSubItem) { + return ( + React.createElement("li", {className: "col-sm-3", key: menuSubItem.id}, + + React.createElement("ul", null, + React.createElement("li", {className: "dropdown-header"}, React.createElement("a", {href: "#", onClick: self.onMenuItemClick.bind(self, menuSubItem)}, React.createElement("p", null, menuSubItem.title))), + menuSubItem.menu_sub_sub_items.map(function(menuSubSubItem) { + return (React.createElement("li", null, React.createElement("a", {href: "#", onClick: self.onMenuItemClick.bind(self, menuSubSubItem)}, menuSubSubItem.title))) + }) + ) + ) + ) + }) + ) + ) + }) + ) + ) + ) + ) + ) + ); + } +}); + +module.exports = MenuItemListComponent; + + +},{"../../actions/menuItemActions":"/home/senadu/projects/ribica/front-ui/app/actions/menuItemActions.js","../../actions/navigationActions":"/home/senadu/projects/ribica/front-ui/app/actions/navigationActions.js","../../models/menuItem":"/home/senadu/projects/ribica/front-ui/app/models/menuItem.js","../../models/menuItemCollection":"/home/senadu/projects/ribica/front-ui/app/models/menuItemCollection.js","../../stores/cartStore.js":"/home/senadu/projects/ribica/front-ui/app/stores/cartStore.js","../../stores/menuItemStore":"/home/senadu/projects/ribica/front-ui/app/stores/menuItemStore.js","../../stores/navigationStore":"/home/senadu/projects/ribica/front-ui/app/stores/navigationStore.js","backbone":"/home/senadu/projects/ribica/front-ui/node_modules/backbone/backbone.js","react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js"}],"/home/senadu/projects/ribica/front-ui/app/components/shared/ribicaFormError.js":[function(require,module,exports){ +var React = require('react'); + + +var RibicaFormError = React.createClass({displayName: "RibicaFormError", + render: function() { + var errorMessages = this.props.errorMessagesObject || {}; + var componentName = this.props.componentName; + var message = errorMessages[componentName]; + if (message !== undefined && message !== null && message !== "") { + return(React.createElement("div", {className: "error-message"}, message)) + } + else return (React.createElement("span", null)); + } +}); + + +module.exports = RibicaFormError; + + +},{"react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js"}],"/home/senadu/projects/ribica/front-ui/app/components/shared/searchBox.js":[function(require,module,exports){ +var React = require('react'), + Router = require('react-router'); + +var NavigationActions = require('../../actions/navigationActions'); +var SearchActions = require('../../actions/searchActions'); +var SearchStore = require('../../stores/searchStore'); + +var SearchBox = React.createClass({displayName: "SearchBox", + getInitialState: function() { + return SearchStore.getSearchBoxState(); + }, + onSearchClick: function(e) { + this.doSearch(); + e.preventDefault(); + }, + componentDidMount: function() { + SearchStore.addChangeListener(this.onSearchStoreChange); + }, + componentWillUnmount: function() { + SearchStore.removeChangeListener(this.onSearchStoreChange); + }, + onSearchStoreChange: function() { + if(this.isMounted()) { + this.setState(SearchStore.getSearchBoxState()); + } + }, + onSearchBoxChange: function(e) { + SearchActions.searchBoxChange(e.currentTarget.value); + }, + doSearch: function() { + if(this.state.q.trim() !== '') { + NavigationActions.goToSearchResults(this.state.q); + } + }, + onKeyPress: function(e) { + var enterKeyCode = 13; + if(e.which == enterKeyCode) { + this.doSearch(); + e.preventDefault(); + } + }, + render: function() { + return (React.createElement("form", {style: {marginLeft: '60px', width: '100%'}, className: "form-inline"}, + React.createElement("div", {className: "left-inner-addon"}, + React.createElement("i", {className: "glyphicon glyphicon-search"}), + React.createElement("input", {style: {width: '75%'}, type: "search", onKeyPress: this.onKeyPress, + className: "search-box form-control", + value: this.state.q, onChange: this.onSearchBoxChange, + "aria-hidden": "true"} + + ), + + React.createElement("button", {className: "btn btn-default search-button", type: "button", + onClick: this.onSearchClick}, "Traži") + + ) + )) + } +}); + +module.exports = SearchBox; + + +},{"../../actions/navigationActions":"/home/senadu/projects/ribica/front-ui/app/actions/navigationActions.js","../../actions/searchActions":"/home/senadu/projects/ribica/front-ui/app/actions/searchActions.js","../../stores/searchStore":"/home/senadu/projects/ribica/front-ui/app/stores/searchStore.js","react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js","react-router":"/home/senadu/projects/ribica/front-ui/node_modules/react-router/modules/index.js"}],"/home/senadu/projects/ribica/front-ui/app/components/shared/sectionsListComponent.js":[function(require,module,exports){ +var React = require('react'), + SectionCollection = require('../../models/sectionCollection'), + Section = require('../../models/section'), + Category = require('../../models/category'), + Backbone = require('backbone'), + NavigationStore = require('../../stores/navigationStore'), + SectionStore = require('../../stores/sectionStore'), + SectionActions = require('../../actions/sectionActions'), + NavigationActions = require('../../actions/navigationActions'); + +Backbone.$ = $; + +var SectionsListComponent = React.createClass({displayName: "SectionsListComponent", + + _onChange: function () { + if (this.isMounted()) { + this.setState(SectionStore.getState()); + } + }, + + getInitialState: function() { + return SectionStore.getState(); + }, + + componentDidMount: function() { + SectionStore.addChangeListener(this._onChange); + SectionActions.loadSections(); + }, + onMouseOver: function(section) { + SectionActions.setSectionHover(section); + + }, + onMouseOut: function() { + SectionActions.unsetSectionHover(); + }, + onMouseLeave: function() { + SectionActions.unsetSectionHover(); + }, + onSectionClick: function(section) { + SectionActions.unsetSectionHover(); + NavigationActions.goToSection(section); + event.preventDefault(); + }, + onCategoryClick: function(category, section) { + SectionActions.unsetSectionHover(); + NavigationActions.goToCategory(new Category(category), section); + event.preventDefault(); + }, + onSubcategoryClick: function(subcategory) { + // implement in navigation actions + // and call + // when ready + return false; + }, + render: function() { + var self = this; + var style = { + position: 'relative' + }; + var abStyle = { + position: 'absolute' + }; + return ( + React.createElement("div", null, + React.createElement("ul", {className: "nav nav-pills"}, + this.state.sections.map(function(section) { + + return ( + React.createElement("li", {key: section.get('id'), onMouseLeave: self.onMouseOut, onMouseOver: self.onMouseOver.bind(self, section), role: "presentation", style: style}, + React.createElement("a", {href: "#", onClick: self.onSectionClick.bind(self, section)}, + section.get('name') + + ), + React.createElement("div", {style: abStyle, className: section.get('id') !== self.state.hoveredSection ? "hide section-cat-list": "section-cat-list"}, + + React.createElement("ul", null, + section.get('categories').map(function(category) { + return ( + React.createElement("li", {key: category.id}, + React.createElement("a", {onClick: self.onCategoryClick.bind(self, category, section)}, category.name) + ) + ) + }) + ) + ) + ) + ) + }) + ) + ) + ); + } +}); + +module.exports = SectionsListComponent; + + +},{"../../actions/navigationActions":"/home/senadu/projects/ribica/front-ui/app/actions/navigationActions.js","../../actions/sectionActions":"/home/senadu/projects/ribica/front-ui/app/actions/sectionActions.js","../../models/category":"/home/senadu/projects/ribica/front-ui/app/models/category.js","../../models/section":"/home/senadu/projects/ribica/front-ui/app/models/section.js","../../models/sectionCollection":"/home/senadu/projects/ribica/front-ui/app/models/sectionCollection.js","../../stores/navigationStore":"/home/senadu/projects/ribica/front-ui/app/stores/navigationStore.js","../../stores/sectionStore":"/home/senadu/projects/ribica/front-ui/app/stores/sectionStore.js","backbone":"/home/senadu/projects/ribica/front-ui/node_modules/backbone/backbone.js","react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js"}],"/home/senadu/projects/ribica/front-ui/app/components/startPage/startPage.js":[function(require,module,exports){ +var React = require('react'), + Router = require('react-router'), + RouteHandler = Router.RouteHandler, + AllItems = require('../items/allItems'), + LinkBanner = require('../linkBanner/linkBanner'), + AllItemsInGroup = require('../items/allItemsInGroup'); + +var StartPage = React.createClass({displayName: "StartPage", + render : function() { + return ( + React.createElement("div", null, + + React.createElement("div", {className: "col-md-12"}, + React.createElement(LinkBanner, {locationName: "startPage"}), + React.createElement(AllItemsInGroup, null), + React.createElement(RouteHandler, null) + ) + ) + ) + } +}); + +module.exports = StartPage; + + +},{"../items/allItems":"/home/senadu/projects/ribica/front-ui/app/components/items/allItems.js","../items/allItemsInGroup":"/home/senadu/projects/ribica/front-ui/app/components/items/allItemsInGroup.js","../linkBanner/linkBanner":"/home/senadu/projects/ribica/front-ui/app/components/linkBanner/linkBanner.js","react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js","react-router":"/home/senadu/projects/ribica/front-ui/node_modules/react-router/modules/index.js"}],"/home/senadu/projects/ribica/front-ui/app/components/thankyou/thankYouPage.js":[function(require,module,exports){ +var React = require('react'), + CartStore = require('../../stores/cartStore'), + AddToCart = require('../cart/addToCart'), + CartActions = require('../../actions/cartActions'), + LinkBanner = require('../linkBanner/linkBanner'), + NavigationActions = require('../../actions/navigationActions'), + Globals = require('../../globals') + Router = require("react-router"), + Link = Router.Link; + + +var ThankYouPage = React.createClass({displayName: "ThankYouPage", + + render: function() { + + return ( + + React.createElement("div", {className: "thank-you-page center"}, + React.createElement("h1", null, "Roba je naručena!"), + React.createElement("p", null, "Hvala na narudžbi. Naša zaposlenica će vas kontaktirati da ugovori detalje o preuzimanju. "), + + React.createElement("p", null, React.createElement(LinkBanner, {locationName: "thankYouPage"})) + + /* +

    Registrujte se kako biste dobili informaciju o popustima, imali pregled svih vaših narudžbi, + koristili Baby Shower, Predlagač poklona te kalendar događaja.

    + +

    + Registracija je jednostavna - samo jedan klik. Klikni ovdje. +

    + */ + ) + + ); + + } + +}); + + +module.exports = ThankYouPage; + + +},{"../../actions/cartActions":"/home/senadu/projects/ribica/front-ui/app/actions/cartActions.js","../../actions/navigationActions":"/home/senadu/projects/ribica/front-ui/app/actions/navigationActions.js","../../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","../../stores/cartStore":"/home/senadu/projects/ribica/front-ui/app/stores/cartStore.js","../cart/addToCart":"/home/senadu/projects/ribica/front-ui/app/components/cart/addToCart.js","../linkBanner/linkBanner":"/home/senadu/projects/ribica/front-ui/app/components/linkBanner/linkBanner.js","react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js","react-router":"/home/senadu/projects/ribica/front-ui/node_modules/react-router/modules/index.js"}],"/home/senadu/projects/ribica/front-ui/app/constants/bySubCategoryConstants.js":[function(require,module,exports){ +var keyMirror = require('react/lib/keyMirror'); + +// Define action constants +module.exports = keyMirror({ + LOAD: null, + FILTER_CRITERIA_CLICK: null, + REMOVE_APPLIED_FILTER: null, + CHANGE_PAGE: null +}); + + +},{"react/lib/keyMirror":"/home/senadu/projects/ribica/front-ui/node_modules/react/lib/keyMirror.js"}],"/home/senadu/projects/ribica/front-ui/app/constants/cartConstants.js":[function(require,module,exports){ +var keyMirror = require('react/lib/keyMirror'); + +// Define action constants +module.exports = keyMirror({ + LOAD_CART_CONTENTS: null, + CART_DATA_LOADED: null, + SAVE_CART_STATE_FOR_ITEM: null, + CHANGE_DELIVERY_DESTINATION_PROPERTY: null, + CONFIRM_DELIVERY: null, + SET_ITEM_COUNT: null, + ADD_N_ITEMS: null, + REMOVE_ITEM: null, + SET_ADDRESS_COLAPSED: null +}); + + +},{"react/lib/keyMirror":"/home/senadu/projects/ribica/front-ui/node_modules/react/lib/keyMirror.js"}],"/home/senadu/projects/ribica/front-ui/app/constants/categoryConstants.js":[function(require,module,exports){ +var keyMirror = require('react/lib/keyMirror'); + +// Define action constants +module.exports = keyMirror({ + LOAD_CATEGORY_DETAILS: null +}); + + +},{"react/lib/keyMirror":"/home/senadu/projects/ribica/front-ui/node_modules/react/lib/keyMirror.js"}],"/home/senadu/projects/ribica/front-ui/app/constants/initializationConstants.js":[function(require,module,exports){ +var keyMirror = require('react/lib/keyMirror'); + +// Define action constants +module.exports = keyMirror({ + INITIALIZE: null +}); + +},{"react/lib/keyMirror":"/home/senadu/projects/ribica/front-ui/node_modules/react/lib/keyMirror.js"}],"/home/senadu/projects/ribica/front-ui/app/constants/itemConstants.js":[function(require,module,exports){ +var keyMirror = require('react/lib/keyMirror'); + +// Define action constants +module.exports = keyMirror({ + LOAD_FOR_FRONTPAGE: null, + LOAD_BSI_FOR_SECTION: null, + LOAD_BSI_FOR_ITEM_GROUP: null, + LOAD_BY_CATEGORY: null +}); + + +},{"react/lib/keyMirror":"/home/senadu/projects/ribica/front-ui/node_modules/react/lib/keyMirror.js"}],"/home/senadu/projects/ribica/front-ui/app/constants/itemDetailsConstants.js":[function(require,module,exports){ +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 +}); + +},{"react/lib/keyMirror":"/home/senadu/projects/ribica/front-ui/node_modules/react/lib/keyMirror.js"}],"/home/senadu/projects/ribica/front-ui/app/constants/menuItemConstants.js":[function(require,module,exports){ +var keyMirror = require('react/lib/keyMirror'); + +// Define action constants +module.exports = keyMirror({ + LOAD_MENU_ITEMS: null, + SET_MENU_ITEM_HOVER: null, + UNSET_MENU_ITEM_HOVER: null +}); + + +},{"react/lib/keyMirror":"/home/senadu/projects/ribica/front-ui/node_modules/react/lib/keyMirror.js"}],"/home/senadu/projects/ribica/front-ui/app/constants/navigationConstants.js":[function(require,module,exports){ +var keyMirror = require('react/lib/keyMirror'); + +// Define action constants +module.exports = keyMirror({ + CHANGE_URL: null +}); + +},{"react/lib/keyMirror":"/home/senadu/projects/ribica/front-ui/node_modules/react/lib/keyMirror.js"}],"/home/senadu/projects/ribica/front-ui/app/constants/searchConstants.js":[function(require,module,exports){ +var keyMirror = require('react/lib/keyMirror'); + +// Define action constants +module.exports = keyMirror({ + SEARCH_BOX_CHANGE: null, + GET_SEARCH_RESULTS: null +}); + + +},{"react/lib/keyMirror":"/home/senadu/projects/ribica/front-ui/node_modules/react/lib/keyMirror.js"}],"/home/senadu/projects/ribica/front-ui/app/constants/sectionConstants.js":[function(require,module,exports){ +var keyMirror = require('react/lib/keyMirror'); + +// Define action constants +module.exports = keyMirror({ + LOAD_SECTIONS: null, + SET_SECTION_HOVER: null, + UNSET_SECTION_HOVER: null, + LOAD_SECTION_DETAILS: null +}); + + +},{"react/lib/keyMirror":"/home/senadu/projects/ribica/front-ui/node_modules/react/lib/keyMirror.js"}],"/home/senadu/projects/ribica/front-ui/app/constants/userConstants.js":[function(require,module,exports){ +var keyMirror = require('react/lib/keyMirror'); + +// Define action constants +module.exports = keyMirror({ + REGISTER_USER : null, + REGISTRATION_SUCCESS: null, + REGISTRATION_FAILURE: null, + USER_LOGIN: null, + LOGIN_SUCCESS: null, + LOGIN_FAILURE: null, + CHECK_LOGIN: null, + CHECK_LOGIN_ARRIVED: null, + USER_LOGOUT_DONE: null, + USER_LOGOUT: null +}); + + +},{"react/lib/keyMirror":"/home/senadu/projects/ribica/front-ui/node_modules/react/lib/keyMirror.js"}],"/home/senadu/projects/ribica/front-ui/app/dispatcher/appDispatcher.js":[function(require,module,exports){ +var Dispatcher = require('flux').Dispatcher; + +// Create dispatcher instance +var AppDispatcher = new Dispatcher(); + +// Convenience method to handle dispatch requests +AppDispatcher.handleAction = function(action) { + this.dispatch({ + source: 'VIEW_ACTION', + action: action + }); + + +} + +module.exports = AppDispatcher; + +},{"flux":"/home/senadu/projects/ribica/front-ui/node_modules/flux/index.js"}],"/home/senadu/projects/ribica/front-ui/app/externalApi.js":[function(require,module,exports){ +var App = function() { + this.bootstrap = function() { + // here goes all app initialization and bootstraping logic + // nothing at the moment + }; +}; + +var app = new App(); +module.exports = app; + + +},{}],"/home/senadu/projects/ribica/front-ui/app/globals.js":[function(require,module,exports){ +module.exports = { + ApiUrl: 'http://localhost:4567', + DefaultPageSize: 24, + ItemGroupIdOfStartPage: "1", + ItemGroupIdOfEmptyCartPage: "1", + FormatCurrency: function(amount_s) { + var amount = parseFloat(amount_s); + return ( amount.toFixed(2) + " KM" ) + }, + MaxNumberOfItemsToBeAdded: 1000 +}; + + +},{}],"/home/senadu/projects/ribica/front-ui/app/models/cart.js":[function(require,module,exports){ +var Backbone = require('backbone'); +var Globals = require('../globals'); + +var Cart = Backbone.Model.extend({ + + initialize: function() { + $.ajaxPrefilter( + function(options, originalOptions, jqXHR) { + options.xhrFields = { + withCredentials: true + } + } + ); + }, + urlRoot : Globals.ApiUrl + '/cart', + defaults : { + 'yes': 'yes' + } +}); + + +module.exports = Cart; + + +},{"../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","backbone":"/home/senadu/projects/ribica/front-ui/node_modules/backbone/backbone.js"}],"/home/senadu/projects/ribica/front-ui/app/models/category.js":[function(require,module,exports){ +var Backbone = require('backbone'); +var Globals = require('../globals'); + +var Category = Backbone.Model.extend({ + urlRoot : Globals.ApiUrl + '/category', + defaults : { + name: '', + filter_criterias: [], + sub_categories: [] + } +}); + + +module.exports = Category; + + +},{"../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","backbone":"/home/senadu/projects/ribica/front-ui/node_modules/backbone/backbone.js"}],"/home/senadu/projects/ribica/front-ui/app/models/categoryCollection.js":[function(require,module,exports){ + + +},{}],"/home/senadu/projects/ribica/front-ui/app/models/deliveryDestination.js":[function(require,module,exports){ +var Backbone = require('backbone'); +var Globals = require('../globals'); + +var DeliveryDestination = Backbone.Model.extend({ + + initialize: function() { + $.ajaxPrefilter( + function(options, originalOptions, jqXHR) { + options.xhrFields = { + withCredentials: true + } + } + ); + }, + + url: Globals.ApiUrl + '/cart/delivery_destination', + defaults: { + count: 0 + } +}); + +module.exports = DeliveryDestination; + +},{"../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","backbone":"/home/senadu/projects/ribica/front-ui/node_modules/backbone/backbone.js"}],"/home/senadu/projects/ribica/front-ui/app/models/item.js":[function(require,module,exports){ +var Backbone = require('backbone'); +var Globals = require('../globals'); + +var Item = Backbone.Model.extend({ + urlRoot : Globals.ApiUrl + '/item', + defaults : { + brand: {} + } + + + +}); + +module.exports = Item; + + + +},{"../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","backbone":"/home/senadu/projects/ribica/front-ui/node_modules/backbone/backbone.js"}],"/home/senadu/projects/ribica/front-ui/app/models/itemCollection.js":[function(require,module,exports){ +var Backbone = require('backbone'), + Item = require('./item'), + Globals = require('../globals'); + +var ItemCollection = Backbone.Collection.extend({ + + initialize: function() { + $.ajaxPrefilter( + function(options, originalOptions, jqXHR) { + options.xhrFields = { + withCredentials: true + } + } + ); + }, + setTotalCount: function(total) { + this.totalCount = total; + }, + addFilter: function(name, value) { + this.filters = this.filters || {}; + this.filters[name] = value; + }, + clearFilter: function() { + this.filters = []; + }, + setLimit: function(limit) { + this.queryLimit = limit; + }, + + setOffset: function(offset) { + this.offset = offset; + }, + + classificationTypeUrlParts: ['', 'section', 'category', 'sub_category', 'item_group'], + + setClassificationType: function(type) { + this.classificationType = type; + }, + + setClassificationId: function(id) { + this.classificationId = id; + }, + + setFromCart: function(fromCart) { + this.fromCart = fromCart; + }, + + model: Item, + url: function() { + if (this.fromCart === true) { + return Globals.ApiUrl + "/cart/item/display"; + } + + var path = '/item'; + + if (this.classificationType > 0) { + // eg. http://localhost:4567/item/section/1/offset/0/limit/10 + var urlPart = this.classificationTypeUrlParts[this.classificationType]; + path += "/" + urlPart + "/" + this.classificationId; + } // else eg. http://localhost:4567/item/offset/0/limit/10 + path += "/offset/" + this.offset + "/limit/" + this.queryLimit; + + var queryParts = []; + + for (var key in this.filters) { + if (this.filters.hasOwnProperty(key)) { + queryParts.push(key + '=' + this.filters[key]); + } + } + var query = ''; + + if (queryParts.length > 0) { + query = '?' + queryParts.join('&'); + } + + return Globals.ApiUrl + path + query; + } +}); + +module.exports = ItemCollection; + + +},{"../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","./item":"/home/senadu/projects/ribica/front-ui/app/models/item.js","backbone":"/home/senadu/projects/ribica/front-ui/node_modules/backbone/backbone.js"}],"/home/senadu/projects/ribica/front-ui/app/models/itemInCart.js":[function(require,module,exports){ +var Backbone = require('backbone'); +var Globals = require('../globals'); + +var ItemInCart = Backbone.Model.extend({ + + initialize: function() { + $.ajaxPrefilter( + function(options, originalOptions, jqXHR) { + options.xhrFields = { + withCredentials: true + } + } + ); + }, + + url: Globals.ApiUrl + '/cart/item', + defaults: { + count: 0 + } +}); + +module.exports = ItemInCart; + +},{"../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","backbone":"/home/senadu/projects/ribica/front-ui/node_modules/backbone/backbone.js"}],"/home/senadu/projects/ribica/front-ui/app/models/itemInCartCollection.js":[function(require,module,exports){ +var Backbone = require('backbone'), + ItemInCart = require('./itemInCart'), + Globals = require('../globals'); + +var ItemInCartCollection = Backbone.Collection.extend({ + + initialize: function() { + $.ajaxPrefilter( + function(options, originalOptions, jqXHR) { + options.xhrFields = { + withCredentials: true + } + } + ); + }, + + model: ItemInCart, + url: Globals.ApiUrl + '/cart/item' + + +}); + +module.exports = ItemInCartCollection; + +},{"../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","./itemInCart":"/home/senadu/projects/ribica/front-ui/app/models/itemInCart.js","backbone":"/home/senadu/projects/ribica/front-ui/node_modules/backbone/backbone.js"}],"/home/senadu/projects/ribica/front-ui/app/models/itemSearchCollection.js":[function(require,module,exports){ +var Backbone = require('backbone'), + Item = require('./item'), + Globals = require('../globals'); + +var ItemSearchCollection = Backbone.Collection.extend({ + initialize: function() { + $.ajaxPrefilter( + function(options, originalOptions, jqXHR) { + options.xhrFields = { + withCredentials: true + } + } + ); + }, + setQuery: function(q) { + this.q = q; + }, + model: Item, + url: function() { + return Globals.ApiUrl + "/search?q=" + this.q; + } +}); + +module.exports = ItemSearchCollection; + + +},{"../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","./item":"/home/senadu/projects/ribica/front-ui/app/models/item.js","backbone":"/home/senadu/projects/ribica/front-ui/node_modules/backbone/backbone.js"}],"/home/senadu/projects/ribica/front-ui/app/models/itemWithDetails.js":[function(require,module,exports){ +var Backbone = require('backbone'); +var Globals = require('../globals'); +var Mutators = require('Backbone.Mutators'); + +var ItemWithDetails = Backbone.Model.extend({ + + urlRoot: Globals.ApiUrl + '/item', + mutators: { + pricePerUnit: function() { + var unitsInPack = this.get('units_in_pack'); + if (unitsInPack == undefined || unitsInPack <= 1) return ""; + unitsInPack = parseFloat(unitsInPack).toFixed(0); + var price = parseFloat(this.get('list_price')).toFixed(2) + var pricePerUnit = (price / unitsInPack).toFixed(2); + var descriptionSuffix = this.get('unit').description_suffix; + return Globals.FormatCurrency(pricePerUnit); + } + }, + defaults : { + brand: {} + } + +}); + +module.exports = ItemWithDetails; + + +},{"../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","Backbone.Mutators":"/home/senadu/projects/ribica/front-ui/node_modules/Backbone.Mutators/backbone.mutators.js","backbone":"/home/senadu/projects/ribica/front-ui/node_modules/backbone/backbone.js"}],"/home/senadu/projects/ribica/front-ui/app/models/linkBanner.js":[function(require,module,exports){ +var Backbone = require('backbone'); +var Globals = require('../globals'); +var LinkBanner = Backbone.Model.extend({ + urlRoot : Globals.ApiUrl + '/link_banner' +}); + +module.exports = LinkBanner; + + + +},{"../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","backbone":"/home/senadu/projects/ribica/front-ui/node_modules/backbone/backbone.js"}],"/home/senadu/projects/ribica/front-ui/app/models/linkBannerCollection.js":[function(require,module,exports){ +var Backbone = require('backbone'), + LinkBanner = require('./linkBanner'), + Globals = require('../globals'); + +var LinkBannerCollection = Backbone.Collection.extend({ + model: LinkBanner, + url: Globals.ApiUrl + '/link_banner' +}); + +module.exports = LinkBannerCollection; + + +},{"../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","./linkBanner":"/home/senadu/projects/ribica/front-ui/app/models/linkBanner.js","backbone":"/home/senadu/projects/ribica/front-ui/node_modules/backbone/backbone.js"}],"/home/senadu/projects/ribica/front-ui/app/models/menuItem.js":[function(require,module,exports){ +var Backbone = require('backbone'); +var Globals = require('../globals'); +var MenuItem = Backbone.Model.extend({ + urlRoot : Globals.ApiUrl + '/menu_item', + defaults: { + title: '', + url: '', + sub_menu_items : [] + } +}); + +module.exports = MenuItem; + + + +},{"../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","backbone":"/home/senadu/projects/ribica/front-ui/node_modules/backbone/backbone.js"}],"/home/senadu/projects/ribica/front-ui/app/models/menuItemCollection.js":[function(require,module,exports){ +var Backbone = require('backbone'), + MenuItem = require('./menuItem'), + Globals = require('../globals'); + +var MenuItemCollection = Backbone.Collection.extend({ + model: MenuItem, + url: Globals.ApiUrl + '/menuitem' +}); + +module.exports = MenuItemCollection; + + +},{"../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","./menuItem":"/home/senadu/projects/ribica/front-ui/app/models/menuItem.js","backbone":"/home/senadu/projects/ribica/front-ui/node_modules/backbone/backbone.js"}],"/home/senadu/projects/ribica/front-ui/app/models/orderConfirmation.js":[function(require,module,exports){ +var Backbone = require('backbone'); +var Globals = require('../globals'); + +var orderConfirmation = Backbone.Model.extend({ + + initialize: function() { + $.ajaxPrefilter( + function(options, originalOptions, jqXHR) { + options.xhrFields = { + withCredentials: true + } + } + ); + }, + + url: Globals.ApiUrl + '/cart/confirmation', + defaults: { } +}); + +module.exports = orderConfirmation; + +},{"../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","backbone":"/home/senadu/projects/ribica/front-ui/node_modules/backbone/backbone.js"}],"/home/senadu/projects/ribica/front-ui/app/models/place.js":[function(require,module,exports){ +var Globals = require('../globals'); +var Backbone = require('backbone'); +var _ = require('underscore'); + +var FREE_SHIPPING_LIMIT = 50; + +var Place = Backbone.Model.extend({ + + initialize: function(options) { + options || (options = {}); + this.postalCode = options.postalCode; + }, + + url: function() { + var postalCode = this.postalCode || "00000"; + return Globals.ApiUrl + '/place/' + postalCode.trim(); + } + +}); + + + +module.exports = Place; + +},{"../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","backbone":"/home/senadu/projects/ribica/front-ui/node_modules/backbone/backbone.js","underscore":"/home/senadu/projects/ribica/front-ui/node_modules/underscore/underscore.js"}],"/home/senadu/projects/ribica/front-ui/app/models/section.js":[function(require,module,exports){ +var Backbone = require('backbone'); +var Globals = require('../globals'); + +var Section = Backbone.Model.extend({ + urlRoot : Globals.ApiUrl + '/section', + defaults : { + name: '', + categories: [] + } +}); + + +module.exports = Section; + + +},{"../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","backbone":"/home/senadu/projects/ribica/front-ui/node_modules/backbone/backbone.js"}],"/home/senadu/projects/ribica/front-ui/app/models/sectionCollection.js":[function(require,module,exports){ +var Backbone = require('backbone'), + Section = require('./section'), + Globals = require('../globals'); + +var SectionCollection = Backbone.Collection.extend({ + model: Section, + url: Globals.ApiUrl + '/section' +}); + +module.exports = SectionCollection; + + +},{"../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","./section":"/home/senadu/projects/ribica/front-ui/app/models/section.js","backbone":"/home/senadu/projects/ribica/front-ui/node_modules/backbone/backbone.js"}],"/home/senadu/projects/ribica/front-ui/app/models/subCategory.js":[function(require,module,exports){ +var Backbone = require('backbone'); +var Globals = require('../globals'); + +var SubCategory = Backbone.Model.extend({ + urlRoot : Globals.ApiUrl + '/subcategory', + defaults : { + name: '', + filter_criterias: [] + } +}); + + +module.exports = SubCategory; + + +},{"../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","backbone":"/home/senadu/projects/ribica/front-ui/node_modules/backbone/backbone.js"}],"/home/senadu/projects/ribica/front-ui/app/ribica.js":[function(require,module,exports){ +var Backbone = require('backbone'); +var React = require('react'); +var ExternalApi = require('./externalApi'); +var Router = require('./router'); +Backbone.$ = $; + +Router.run(function(Handler, state) { + React.render(React.createElement(Handler, null), document.body); +}); + +module.exports = { + App: ExternalApi, + Router: Router +} + + +},{"./externalApi":"/home/senadu/projects/ribica/front-ui/app/externalApi.js","./router":"/home/senadu/projects/ribica/front-ui/app/router.js","backbone":"/home/senadu/projects/ribica/front-ui/node_modules/backbone/backbone.js","react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js"}],"/home/senadu/projects/ribica/front-ui/app/router.js":[function(require,module,exports){ +var React = require('react'); +var Router = require('react-router'), + Route = Router.Route, DefaultRoute = Router.DefaultRoute; +var RouteHandler = Router.RouteHandler; +var Navigation = Router.Navigation; + +var ItemWithDetailsPage = require('./components/items/itemWithDetailsPage'); +var ItemList = require('./components/items/itemList'); +var SectionsListComponent = require('./components/shared/sectionsListComponent'); +var AllItems = require('./components/items/allItems'); +var ItemGroupPage = require('./components/items/itemGroupPage'); +var CartPage = require('./components/cart/cartPage'); +var CheckoutPage = require('./components/cart/checkoutPage'); +var RootApp = require('./components/rootApp'); +var StartPage = require('./components/startPage/startPage'); +var ByCategory = require('./components/browsing/byCategory'); +var BySubCategory = require('./components/browsing/bySubCategory'); +var BySection = require('./components/browsing/bySection'); +var ThankYouPage = require('./components/thankyou/thankYouPage'); +// var Register = require('./components/account/register'); +// var Login = require('./components/account/login'); +var SearchResultsPage = require('./components/search/searchResultsPage'); + + +var routes = ( + React.createElement(Route, {name: "app", path: "/", handler: RootApp}, + React.createElement(Route, {name: "sekcija", path: "sekcija/:id/:name", handler: BySection}), + React.createElement(Route, {name: "artikal", path: "artikal/:id/*", handler: ItemWithDetailsPage}), + React.createElement(Route, {name: "grupa", path: "grupa/:id/*", handler: ItemGroupPage}), + React.createElement(Route, {name: "korpa", path: "/korpa", handler: CartPage}), + React.createElement(Route, {name: "dostava", path: "/dostava", handler: CheckoutPage}), + /**/ + /**/ + React.createElement(Route, {name: "podkategorija", path: "/podkategorija/:id/*", handler: BySubCategory}), + React.createElement(Route, {name: "byCat", path: "sekcija/:sekcijaName/kategorija/:id/*", handler: ByCategory}), + React.createElement(Route, {name: "hvala", path: "/hvala", handler: ThankYouPage}), + React.createElement(Route, {name: "pretraga", path: "/pretraga", handler: SearchResultsPage}), + React.createElement(DefaultRoute, {handler: StartPage}) + ) + ); + +var router = Router.create({ + routes: routes, + location: Router.HistoryLocation +}); + +module.exports = router; + + +},{"./components/browsing/byCategory":"/home/senadu/projects/ribica/front-ui/app/components/browsing/byCategory.js","./components/browsing/bySection":"/home/senadu/projects/ribica/front-ui/app/components/browsing/bySection.js","./components/browsing/bySubCategory":"/home/senadu/projects/ribica/front-ui/app/components/browsing/bySubCategory.js","./components/cart/cartPage":"/home/senadu/projects/ribica/front-ui/app/components/cart/cartPage.js","./components/cart/checkoutPage":"/home/senadu/projects/ribica/front-ui/app/components/cart/checkoutPage.js","./components/items/allItems":"/home/senadu/projects/ribica/front-ui/app/components/items/allItems.js","./components/items/itemGroupPage":"/home/senadu/projects/ribica/front-ui/app/components/items/itemGroupPage.js","./components/items/itemList":"/home/senadu/projects/ribica/front-ui/app/components/items/itemList.js","./components/items/itemWithDetailsPage":"/home/senadu/projects/ribica/front-ui/app/components/items/itemWithDetailsPage.js","./components/rootApp":"/home/senadu/projects/ribica/front-ui/app/components/rootApp.js","./components/search/searchResultsPage":"/home/senadu/projects/ribica/front-ui/app/components/search/searchResultsPage.js","./components/shared/sectionsListComponent":"/home/senadu/projects/ribica/front-ui/app/components/shared/sectionsListComponent.js","./components/startPage/startPage":"/home/senadu/projects/ribica/front-ui/app/components/startPage/startPage.js","./components/thankyou/thankYouPage":"/home/senadu/projects/ribica/front-ui/app/components/thankyou/thankYouPage.js","react":"/home/senadu/projects/ribica/front-ui/node_modules/react/react.js","react-router":"/home/senadu/projects/ribica/front-ui/node_modules/react-router/modules/index.js"}],"/home/senadu/projects/ribica/front-ui/app/stores/bySubCategoryStore.js":[function(require,module,exports){ +var AppDispatcher = require('../dispatcher/appDispatcher'); +var EventEmitter = require('events').EventEmitter; +//var SubCategoryCollection = require('../models/subCategoryCollection'); +var SubCategory = require('../models/subCategory'); + +var NavigationActions = require('../actions/navigationActions'); +var BySubCategoryConstants = require('../constants/bySubCategoryConstants'); +var ItemCollection = require('../models/itemCollection'); +var _ = require('underscore'); + +var Globals = require('../globals'); + +var _state = { + subCategory : (new SubCategory()), + items: (new ItemCollection()), + filter: {}, + pagination: { + offset : 0, + limit: Globals.DefaultPageSize + } +}; +//var _categoryDetails = new Category(); + +//var loadSections = function() { + //var sections = new SectionCollection(); + //sections.fetch({success: function() { + //sectionState.sections = sections.models; + //// change will be called automatically when + //// action is run but we need to emit it again + //// when the data arive + //// it's a bit "unfluxy" but convenient. + //// "true philosophy" would be to run another "data arrived" action + //SectionStore.emitChange(); + //}}); +//}; +var fetchItemsBySubCategory = function(subCategoryId, offset, limit, query) { + //var items = _itemsByCategory; + query = query || {}; + var items = new ItemCollection(); + items.clearFilter(); + items.setClassificationType(3); + items.setClassificationId(subCategoryId); + items.setLimit(limit); + items.setOffset(offset); + + for(var key in query) { + if (query.hasOwnProperty(key) && key != 'limit' && key !='offset') { + items.addFilter(key, query[key]); + } + } + + items.fetch({ + success: function(collection, response, options) { + var total = options.xhr.getResponseHeader('x-total-count'); + items.setTotalCount(total); + + _state.items = items; + BySubCategoryStore.emitChange(); + }}); + +}; +var load = function(subCategoryId, offset, limit, filter) { + + var subCategory = new SubCategory({id : subCategoryId}); + subCategory.fetch({ + success: function() { + _state.subCategory = subCategory; + fetchItemsBySubCategory(subCategoryId, offset, limit, filter); + + } + }); + _state.filter = filter; + _state.pagination.limit = limit; + _state.pagination.offset = offset; +}; + +var handleFilterCriteriaClick = function(fc, fcv) { + _state.filter[fc.field_name] = fcv.filter_value; + + setTimeout(function() { + NavigationActions.goToSubCategory(_state.subCategory, 0, _state.pagination.limit, _state.filter); + }, 0); +}; + +var handleRemoveAppliedFilter= function(name) { + delete _state.filter[name]; + + setTimeout(function() { + NavigationActions.goToSubCategory(_state.subCategory, 0, _state.pagination.limit, _state.filter); + }, 0); +}; + +var handleChangePage = function(page) { + setTimeout(function() { + NavigationActions.goToSubCategory(_state.subCategory, parseInt(page) * _state.pagination.limit, _state.pagination.limit, _state.filter); + }, 0); +}; + + + +// Extend SectionStore with EventEmitter to add eventing capabilities +var BySubCategoryStore = _.extend({}, EventEmitter.prototype, { + + getState: function() { + return _state; + }, + // Emit Change event + emitChange: function() { + + 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 BySubCategoryConstants.LOAD: + load(action.subCategoryId, action.offset, action.limit, action.filter); + break; + + case BySubCategoryConstants.FILTER_CRITERIA_CLICK: + handleFilterCriteriaClick(action.fc, action.fcv); + break; + case BySubCategoryConstants.REMOVE_APPLIED_FILTER: + handleRemoveAppliedFilter(action.name); + break; + case BySubCategoryConstants.CHANGE_PAGE: + handleChangePage(action.page); + break; + + // Respond to SELECT_ITEM action + //case SectionConstants.LOAD_SECTIONS: + //loadSections(); + //break; + + //case SectionConstants.SET_SECTION_HOVER: + //setHovered(action.section.get('id')); + //break; + + //case SectionConstants.UNSET_SECTION_HOVER: + //setHovered(''); + //break; + + //case CategoryConstants.LOAD_CATEGORY_DETAILS: + //loadCategoryDetails(action.categoryId); + //break; + + default: + return true; + } + + // If action was responded to, emit change event + BySubCategoryStore.emitChange(); + return true; + +}); + +module.exports = BySubCategoryStore; + + +},{"../actions/navigationActions":"/home/senadu/projects/ribica/front-ui/app/actions/navigationActions.js","../constants/bySubCategoryConstants":"/home/senadu/projects/ribica/front-ui/app/constants/bySubCategoryConstants.js","../dispatcher/appDispatcher":"/home/senadu/projects/ribica/front-ui/app/dispatcher/appDispatcher.js","../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","../models/itemCollection":"/home/senadu/projects/ribica/front-ui/app/models/itemCollection.js","../models/subCategory":"/home/senadu/projects/ribica/front-ui/app/models/subCategory.js","events":"/home/senadu/projects/ribica/front-ui/node_modules/events/events.js","underscore":"/home/senadu/projects/ribica/front-ui/node_modules/underscore/underscore.js"}],"/home/senadu/projects/ribica/front-ui/app/stores/cartStore.js":[function(require,module,exports){ +var AppDispatcher = require('../dispatcher/appDispatcher'); +var EventEmitter = require('events').EventEmitter; +var CartConstants = require('../constants/cartConstants'); +var CartActions = require('../actions/cartActions'); +var NavigationActions = require('../actions/navigationActions'); +var ItemInCart = require('../models/itemInCart'); +var ItemInCartCollection = require('../models/itemInCartCollection'); +var ItemCollection = require('../models/itemCollection'); +var DeliveryDestination = require('../models/deliveryDestination'); +var OrderConfirmation = require('../models/orderConfirmation'); +var Place = require('../models/place'); +var Validation = require('../utils/validation'); + +var _ = require('underscore'); + +var states = {} + +var _itemsInCart = new ItemInCartCollection(); +var _itemsForDisplay = new ItemCollection(); +_itemsForDisplay.setFromCart(true); + +var _deliveryDestination = new DeliveryDestination(); +var _deliveryDestinationErrors = {}; +var _deliveryCosts = new Place({ + postalCode: _deliveryDestination.get('place') +}) + +var _addressColapsed = false; + + +var supportedPlaces = [ +{ + "code": "-12", + "placeLabel": "Izaberite mjesto" +}, { + "code": "-13", + "placeLabel": "-------------------------------" +}, { + "code": " 71000", + "placeLabel": "Sarajevo" +}, { + "code": " 71103", + "placeLabel": "Sarajevo, Centar" +}, { + "code": " 71160", + "placeLabel": "Sarajevo, Novi Grad" +}, { + "code": " 71120", + "placeLabel": "Sarajevo, Novo Sarajevo" +}, { + "code": " 71140", + "placeLabel": "Sarajevo, Stari Grad" +}, { + "code": " 78000", + "placeLabel": "Banja Luka" +}, { + "code": " 75000", + "placeLabel": "Tuzla" +}, { + "code": " 72000", + "placeLabel": "Zenica" +}, { + "code": " 88000", + "placeLabel": "Mostar" +}, { + "code": " 88000", + "placeLabel": "Mostar, Jug" +}, { + "code": " 88000", + "placeLabel": "Mostar, Jugozapad" +}, { + "code": " 88000", + "placeLabel": "Mostar, Sjever" +}, { + "code": " 88000", + "placeLabel": "Mostar, Zapad" +}, { + "code": "71300", + "placeLabel": "Visoko" +}, { + "code": "71240", + "placeLabel": "Hadžići" +}]; + +var _cartDataLoadCalled = false; + +var nameOfThePlace = function(code) { + for(var i=0; i 0) { + // state.set('count', state.get('count') - 1); + state.set('count', 0); + } + + states[itemId] = state; + saveCartStateForItem(itemId); +}; + +var setItemCount = function(itemId, count) { + var state = states[itemId] || new ItemInCart({ + item_id: itemId, + count: 0 + }); + + if (count === "") { + state.set('count', ""); + CartStore.emitChange(); + return; + } + + var cnt = parseInt(count); + + if (isNaN(cnt) || cnt <= 0) { + cnt = 1; + } + + state.set('count', cnt); + + states[itemId] = state; + saveCartStateForItem(itemId); + // CartStore.emitChange(); +}; + +var addNItems = function(item, count) { + + var itemId = item.get('id'); + var state = states[itemId] || new ItemInCart({ + item_id: itemId, + count: 0 + }) + + _itemsForDisplay.add(item); + + var realCount = state.get('count') + count; + + // remove if we choose to support more than + // 10 items of single type in cart + if (realCount > 10) { + realCount = 10; + + } + state.set('count', realCount); + + states[itemId] = state; + saveCartStateForItem(itemId); +} + +var changeDeliveryDestinationProperty = function(property, value) { + _deliveryDestination.set(property, value); + + if (property === 'place') { + fetchPlace(); + } + validateDeliveryDestinationForm(); +}; + + +var confirmOrder = function() { + + var oc = new OrderConfirmation({ + hamo: 'meho' + }); + oc.save({ + b: 'b' + }, { + success: function() { + + NavigationActions.goToThankYou(); + loadCart(); + } + }); +}; + + +var saveDeliveryDestination = function() { + _deliveryDestination.save(null, { + success: function() { + + confirmOrder(); + } + }) +}; + +var validateDeliveryDestinationForm = function() { + _deliveryDestinationErrors = {}; + + var nameRegex = /.+\s+.+/i; + if (Validation.safeString(_deliveryDestination.get('name')).search(nameRegex) < 0) { + _deliveryDestinationErrors['name'] = "I prezime i ime su obavezni"; + } + + var addressRegex = /.+\s+.+/i; + if (Validation.safeString(_deliveryDestination.get('address')).search(addressRegex) < 0) { + _deliveryDestinationErrors['address'] = "Adresa mora biti ispravna"; + } + + var emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/i; + if (Validation.safeString(_deliveryDestination.get('email')).search(emailRegex) < 0) { + _deliveryDestinationErrors['email'] = "Email mora biti ispravno upisan"; + } + + var phoneRegex = /^[\d\s-]{8,12}$/i; + if (Validation.safeString(_deliveryDestination.get('phone')).search(phoneRegex) < 0) { + _deliveryDestinationErrors['phone'] = "Telefon mora biti ispravan"; + } + + var placeRegex = /^\s{0,1}\d{5}$/i; + if (Validation.safeString(_deliveryDestination.get('place')).search(placeRegex) < 0) { + _deliveryDestinationErrors['place'] = "Mjesto mora biti izabrano"; + } + + var requiredFields = ["name", "email", "place", 'address', 'phone']; + for (var i in requiredFields) { + var value = _deliveryDestination.get(requiredFields[i]); + if (value === undefined || value === null || value === "") { + // if it's required there will be a star there + _deliveryDestinationErrors[requiredFields[i]] = "*"; + } + } + +} + + +var isDeliveryDestinationValid = function() { + return Object.getOwnPropertyNames(_deliveryDestinationErrors).length === 0; + } + // Extend CartStore with EventEmitter to add eventing capabilities +var CartStore = _.extend({}, EventEmitter.prototype, { + + dataStartedLoading: function() { + return _cartDataLoadCalled; + }, + + getStateFor: function(itemId) { + + var state = states[itemId] || new ItemInCart({ + item_id: itemId, + count: 0 + }) + return state + }, + + getSupportedPlaces: function() { + return supportedPlaces; + }, + + getWholeCartState: function() { + + var numberOfItems = 0; + + for (key in states) { + if (states.hasOwnProperty(key)) { + var value = states[key]; + if (value.get('count') > 0) { + numberOfItems += value.get('count'); + } + } + }; + + var state = { + count: numberOfItems, + items: _itemsForDisplay, + itemCounts: states, + deliveryDestination: _deliveryDestination, + deliveryDestinationErrors: _deliveryDestinationErrors, + isDeliveryDestinationValid: isDeliveryDestinationValid(), + deliveryCosts: _deliveryCosts, + destinationValid: isDeliveryDestinationValid() + }; + return state; + }, + + // Emit Change event + emitChange: function() { + this.emit('change'); + }, + + // Add change listener + addChangeListener: function(callback) { + this.on('change', callback); + }, + + // Remove change listener + removeChangeListener: function(callback) { + this.removeListener('change', callback); + }, + + isDeliveryDestinationValid: isDeliveryDestinationValid, + + isAddressColapsed: function() { + return _addressColapsed; + + }, + + getHumanReadableAddress: function() { + var address = []; + address.push(_deliveryDestination.get('name')); + address.push(_deliveryDestination.get('address')); + address.push(_deliveryDestination.get('place') + " " + nameOfThePlace(_deliveryDestination.get('place'))); + address.push("Bosna i Hercegovina"); + address.push("+387" + _deliveryDestination.get('phone')) + address.push(_deliveryDestination.get('email')) + + return address; + } + +}); + + +// Register callback with AppDispatcher +AppDispatcher.register(function(payload) { + var action = payload.action; + var text; + + switch (action.actionType) { + case CartConstants.LOAD_CART_CONTENTS: + loadCart(); + break; + case CartConstants.TAKE_ITEM_OUT: + takeItemOut(action.itemId); + break; + case CartConstants.CART_DATA_LOADED: + // do nothing - just emmit change + break; + case CartConstants.SAVE_CART_STATE_FOR_ITEM: + if (isDeliveryDestinationValid()) { + saveCartStateForItem(action.itemId); + } + break; + case CartConstants.CHANGE_DELIVERY_DESTINATION_PROPERTY: + changeDeliveryDestinationProperty(action.propertyName, action.value) + break; + case CartConstants.CONFIRM_DELIVERY: + saveDeliveryDestination(); + break; + case CartConstants.ADD_N_ITEMS: + addNItems(action.item, action.count); + break; + case CartConstants.SET_ITEM_COUNT: + setItemCount(action.itemId, action.count); + break; + case CartConstants.SET_ADDRESS_COLAPSED: + _addressColapsed = action.isColapsed + break; + default: + return true; + } + + // If action was responded to, emit change event + CartStore.emitChange(); + return true; + +}); + +module.exports = CartStore; + +},{"../actions/cartActions":"/home/senadu/projects/ribica/front-ui/app/actions/cartActions.js","../actions/navigationActions":"/home/senadu/projects/ribica/front-ui/app/actions/navigationActions.js","../constants/cartConstants":"/home/senadu/projects/ribica/front-ui/app/constants/cartConstants.js","../dispatcher/appDispatcher":"/home/senadu/projects/ribica/front-ui/app/dispatcher/appDispatcher.js","../models/deliveryDestination":"/home/senadu/projects/ribica/front-ui/app/models/deliveryDestination.js","../models/itemCollection":"/home/senadu/projects/ribica/front-ui/app/models/itemCollection.js","../models/itemInCart":"/home/senadu/projects/ribica/front-ui/app/models/itemInCart.js","../models/itemInCartCollection":"/home/senadu/projects/ribica/front-ui/app/models/itemInCartCollection.js","../models/orderConfirmation":"/home/senadu/projects/ribica/front-ui/app/models/orderConfirmation.js","../models/place":"/home/senadu/projects/ribica/front-ui/app/models/place.js","../utils/validation":"/home/senadu/projects/ribica/front-ui/app/utils/validation.js","events":"/home/senadu/projects/ribica/front-ui/node_modules/events/events.js","underscore":"/home/senadu/projects/ribica/front-ui/node_modules/underscore/underscore.js"}],"/home/senadu/projects/ribica/front-ui/app/stores/categoryStore.js":[function(require,module,exports){ +var AppDispatcher = require('../dispatcher/appDispatcher'); +var EventEmitter = require('events').EventEmitter; + +var CategoryCollection = require('../models/categoryCollection'); +var Category = require('../models/category'); + +var CategoryConstants = require('../constants/categoryConstants'); +var _ = require('underscore'); + +var _categoryDetails = new Category(); + +//var loadSections = function() { + //var sections = new SectionCollection(); + //sections.fetch({success: function() { + //sectionState.sections = sections.models; + //// change will be called automatically when + //// action is run but we need to emit it again + //// when the data arive + //// it's a bit "unfluxy" but convenient. + //// "true philosophy" would be to run another "data arrived" action + //SectionStore.emitChange(); + //}}); +//}; + +var loadCategoryDetails = function(categoryId) { + var category = new Category({id : categoryId}); + category.fetch({ + success: function() { + _categoryDetails = category; + CategoryStore.emitChange(); + } + }); +}; + +//var setHovered = function(id) { + //sectionState.hoveredSection = id; +//} + + +// Extend SectionStore with EventEmitter to add eventing capabilities +var CategoryStore = _.extend({}, EventEmitter.prototype, { + + getCategoryDetails: function() { + return _categoryDetails; + }, + // Emit Change event + emitChange: function() { + + 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) { + + // Respond to SELECT_ITEM action + //case SectionConstants.LOAD_SECTIONS: + //loadSections(); + //break; + + //case SectionConstants.SET_SECTION_HOVER: + //setHovered(action.section.get('id')); + //break; + + //case SectionConstants.UNSET_SECTION_HOVER: + //setHovered(''); + //break; + + case CategoryConstants.LOAD_CATEGORY_DETAILS: + loadCategoryDetails(action.categoryId); + break; + + default: + return true; + } + + // If action was responded to, emit change event + CategoryStore.emitChange(); + return true; + +}); + +module.exports = CategoryStore; + + +},{"../constants/categoryConstants":"/home/senadu/projects/ribica/front-ui/app/constants/categoryConstants.js","../dispatcher/appDispatcher":"/home/senadu/projects/ribica/front-ui/app/dispatcher/appDispatcher.js","../models/category":"/home/senadu/projects/ribica/front-ui/app/models/category.js","../models/categoryCollection":"/home/senadu/projects/ribica/front-ui/app/models/categoryCollection.js","events":"/home/senadu/projects/ribica/front-ui/node_modules/events/events.js","underscore":"/home/senadu/projects/ribica/front-ui/node_modules/underscore/underscore.js"}],"/home/senadu/projects/ribica/front-ui/app/stores/initializationStore.js":[function(require,module,exports){ +var AppDispatcher = require('../dispatcher/appDispatcher'); +var EventEmitter = require('events').EventEmitter; +var Cart = require('../models/cart'); +var LinkBannerCollection = require('../models/linkBannerCollection'); + +var InitializationConstants = require('../constants/initializationConstants') +var _ = require('underscore'); + +var banners = {}; + + +var state = { + isEverythingReadyToStartTheShow: false +}; + +var initializeTheShop = function () { + // for now only guarantee that cart is created + var cart = new Cart(); + cart.save(null, { + success: function () { + + state.isEverythingReadyToStartTheShow = true; + InitializationStore.emitChange(); + + } + }) + + initializeBanners(); + + +}; + + +var putBannerOnItsPlace = function(banners, lb) { + + + if(lb.get('start_page')) banners['startPage'].push(lb); + if(lb.get('thank_you_page')) banners['thankYouPage'].push(lb); + if(lb.get('search_result_page')) banners['searchResultPage'].push(lb); + if(lb.get('checkout_page')) banners['checkoutPage'].push(lb); + + var itemId = lb.get('item_id'); + if (itemId) { + banners['item'][itemId] = banners['item'][itemId] || []; + banners['item'][itemId].push(lb); + } + + var sectionId = lb.get('section_id'); + if (sectionId) { + banners['section'][sectionId] = banners['section'][sectionId] || []; + banners['section'][sectionId].push(lb); + } + + var categoryId = lb.get('category_id'); + if (categoryId) { + banners['category'][categoryId] = banners['category'][categoryId] || []; + banners['category'][categoryId].push(lb); + } + + var subCategoryId = lb.get('sub_category_id'); + if (subCategoryId) { + banners['subCategory'][subCategoryId] = banners['subCategory'][subCategoryId] || []; + banners['subCategory'][subCategoryId].push(lb); + } + +}; + +var initializeBanners = function() { + banners["startPage"] = []; + banners["searchResultPage"] = []; + banners["thankYouPage"] = []; + banners["checkoutPage"] = []; + banners["header"] = []; + banners["footer"] = []; + banners["section"] = {}; + banners["category"] = {}; + banners["subCategory"] = {}; + banners["item"] = {}; + + var lbc = new LinkBannerCollection(); + lbc.fetch({ + success: function() { + lbc.each(function(linkBanner) { + putBannerOnItsPlace( banners, linkBanner ); + } ); + + InitializationStore.emitChange(); + } + }); + +}; + + + +// Extend ItemStore with EventEmitter to add eventing capabilities +var InitializationStore = _.extend({}, EventEmitter.prototype, { + + getState: function() { + return state; + }, + + + getBanners: function() { + return banners; + }, + + // Emit Change event + emitChange: function() { + + 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 +InitializationStore.dispatchToken = AppDispatcher.register(function(payload) { + var action = payload.action; + + switch (action.actionType) { + + case InitializationConstants.INITIALIZE: + initializeTheShop(); + break; + default: + return true; + } + + // If action was responded to, emit change event + InitializationStore.emitChange(); + return true; + +}); + +module.exports = InitializationStore; + +},{"../constants/initializationConstants":"/home/senadu/projects/ribica/front-ui/app/constants/initializationConstants.js","../dispatcher/appDispatcher":"/home/senadu/projects/ribica/front-ui/app/dispatcher/appDispatcher.js","../models/cart":"/home/senadu/projects/ribica/front-ui/app/models/cart.js","../models/linkBannerCollection":"/home/senadu/projects/ribica/front-ui/app/models/linkBannerCollection.js","events":"/home/senadu/projects/ribica/front-ui/node_modules/events/events.js","underscore":"/home/senadu/projects/ribica/front-ui/node_modules/underscore/underscore.js"}],"/home/senadu/projects/ribica/front-ui/app/stores/itemDetailsStore.js":[function(require,module,exports){ +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); + + return match[1]; +}; + +var fetchItemWithDetails = function() { + var id = getItemIdFromUrl(); + if (id !== undefined && _itemWithDetails.id !== id) { + var item = new ItemWithDetails({ + id: id + }); + _itemWithDetails = item; + item.fetch({ + success: function() { + _images = (_itemWithDetails.get("multi_media_descriptions") || []).map(function(mmd) { + return mmd.resized_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() { + + + var firstImage = _images[0] || "https://res.cloudinary.com/lfvt7ps2n/image/upload/c_fit,h_172,w_226/v1421732950/http_www.asms.ru_bitrix_templates_main_images_nophoto_irnofq.png"; + return { + item: _itemWithDetails, + images: _images, + currentImage: _currentImage, + count: _count, + firstImage: firstImage + } + + }, + + + // item with details + getLoadedItemWithDetails: function() { + return _itemWithDetails; + }, + + // Emit Change event + emitChange: function() { + 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; + +},{"../constants/itemDetailsConstants":"/home/senadu/projects/ribica/front-ui/app/constants/itemDetailsConstants.js","../dispatcher/appDispatcher":"/home/senadu/projects/ribica/front-ui/app/dispatcher/appDispatcher.js","../models/itemWithDetails":"/home/senadu/projects/ribica/front-ui/app/models/itemWithDetails.js","events":"/home/senadu/projects/ribica/front-ui/node_modules/events/events.js","underscore":"/home/senadu/projects/ribica/front-ui/node_modules/underscore/underscore.js"}],"/home/senadu/projects/ribica/front-ui/app/stores/itemStore.js":[function(require,module,exports){ +var AppDispatcher = require('../dispatcher/appDispatcher'); +var EventEmitter = require('events').EventEmitter; +var ItemConstants = require('../constants/itemConstants'); +var ItemCollection = require('../models/itemCollection'); +var ItemWithDetails = require('../models/itemWithDetails'); +var _ = require('underscore'); + +// Define initial data points +var _items = new ItemCollection(), + _itemWithDetails = new ItemWithDetails(), + _bestSellingForSection = new ItemCollection(), + _itemsByCategory = new ItemCollection(), + _bestSellingForGroup = new ItemCollection(); + + +var loadItemsForFrontpage = function() { + items = _items + items.setClassificationType(0); + items.setLimit(30); + items.setOffset(0); + + items.fetch({ + success: function() { + ItemStore.emitChange(); + } + }); +}; + +var 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+)\//g; + var match = itemIdRegex.exec(url); + + if (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; + ItemStore.emitChange(); + } + }); + } +} + +var fetchItemsByCategory = function(categoryId, offset, limit, query) { + //var items = _itemsByCategory; + var items = new ItemCollection(); + items.clearFilter(); + items.setClassificationType(2); + items.setClassificationId(categoryId); + items.setLimit(limit); + items.setOffset(offset); + + for (var key in query) { + if (query.hasOwnProperty(key) && key != 'limit' && key != 'offset') { + items.addFilter(key, query[key]); + } + } + + items.fetch({ + success: function(collection, response, options) { + var total = options.xhr.getResponseHeader('x-total-count'); + items.setTotalCount(total); + + ItemStore.emitChange(); + } + }); + + _itemsByCategory = items; +}; + +var fetchBestSellingItemsForSection = function(sectionId) { + + var items = _bestSellingForSection; + items.setClassificationType(1); + items.setClassificationId(sectionId); + items.setLimit(30); + items.setOffset(0); + + items.fetch({ + success: function() { + ItemStore.emitChange(); + } + }); +}; + + +var fetchBestSellingItemsForGroup = function(groupId) { + + var items = _bestSellingForGroup; + items.setClassificationType(4); + items.setClassificationId(groupId); + items.setLimit(30); + items.setOffset(0); + + items.fetch({ + success: function() { + ItemStore.emitChange(); + } + }); +}; + + +// Extend ItemStore with EventEmitter to add eventing capabilities +var ItemStore = _.extend({}, EventEmitter.prototype, { + + getItemsForCategory: function() { + return _itemsByCategory; + }, + getBestSellingForSection: function() { + + return _bestSellingForSection; + }, + // item with details + getLoadedItemWithDetails: function() { + return _itemWithDetails; + }, + + getItemsForGroup: function() { + return _bestSellingForGroup; + }, + + + // Return All Items + getItems: function() { + return _items; + }, + + // Emit Change event + emitChange: function() { + + 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 ItemConstants.LOAD_ITEM_WITH_DETAILS: + fetchItemWithDetails(); + break; + + case ItemConstants.LOAD_BSI_FOR_SECTION: + fetchBestSellingItemsForSection(action.sectionId); + break; + + case ItemConstants.LOAD_BSI_FOR_ITEM_GROUP: + fetchBestSellingItemsForGroup(action.groupId); + break; + + case ItemConstants.LOAD_FOR_FRONTPAGE: + loadItemsForFrontpage(); + break; + case ItemConstants.LOAD_BY_CATEGORY: + fetchItemsByCategory(action.categoryId, action.offset, action.limit, action.query); + break; + + default: + return true; + } + + // If action was responded to, emit change event + ItemStore.emitChange(); + return true; + +}); + +module.exports = ItemStore; + + +},{"../constants/itemConstants":"/home/senadu/projects/ribica/front-ui/app/constants/itemConstants.js","../dispatcher/appDispatcher":"/home/senadu/projects/ribica/front-ui/app/dispatcher/appDispatcher.js","../models/itemCollection":"/home/senadu/projects/ribica/front-ui/app/models/itemCollection.js","../models/itemWithDetails":"/home/senadu/projects/ribica/front-ui/app/models/itemWithDetails.js","events":"/home/senadu/projects/ribica/front-ui/node_modules/events/events.js","underscore":"/home/senadu/projects/ribica/front-ui/node_modules/underscore/underscore.js"}],"/home/senadu/projects/ribica/front-ui/app/stores/menuItemStore.js":[function(require,module,exports){ +var AppDispatcher = require('../dispatcher/appDispatcher'); +var EventEmitter = require('events').EventEmitter; +var MenuItemCollection = require('../models/menuItemCollection'); +var MenuItem = require('../models/menuItem'); +var MenuItemConstants = require('../constants/menuItemConstants'); +var _ = require('underscore'); + +var menuItemState = { + menuItems : [], + hoveredMenuItem : '' +}; + + +var loadMenuItems = function() { + var menuItems = new MenuItemCollection(); + menuItems.fetch({success: function() { + menuItemState.menuItems = menuItems.models; + // change will be called automatically when + // action is run but we need to emit it again + // when the data arive + // it's a bit "unfluxy" but convenient. + // "true philosophy" would be to run another "data arrived" action + MenuItemStore.emitChange(); + }}); +}; + +var setHovered = function(id) { + menuItemState.hoveredMenuItem = id; +} + + +// Extend MenuItemStore with EventEmitter to add eventing capabilities +var MenuItemStore = _.extend({}, EventEmitter.prototype, { + + // Return Single Item With Details + getState: function() { + return menuItemState; + }, + // Emit Change event + emitChange: function() { + 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) { + + // Respond to SELECT_ITEM action + case MenuItemConstants.LOAD_MENU_ITEMS: + loadMenuItems(); + break; + + case MenuItemConstants.SET_MENU_ITEM_HOVER: + setHovered(action.menuItem.get('id')); + break; + + case MenuItemConstants.UNSET_MENU_ITEM_HOVER: + setHovered(''); + break; + default: + return true; + } + + // If action was responded to, emit change event + MenuItemStore.emitChange(); + return true; + +}); + +module.exports = MenuItemStore; + + +},{"../constants/menuItemConstants":"/home/senadu/projects/ribica/front-ui/app/constants/menuItemConstants.js","../dispatcher/appDispatcher":"/home/senadu/projects/ribica/front-ui/app/dispatcher/appDispatcher.js","../models/menuItem":"/home/senadu/projects/ribica/front-ui/app/models/menuItem.js","../models/menuItemCollection":"/home/senadu/projects/ribica/front-ui/app/models/menuItemCollection.js","events":"/home/senadu/projects/ribica/front-ui/node_modules/events/events.js","underscore":"/home/senadu/projects/ribica/front-ui/node_modules/underscore/underscore.js"}],"/home/senadu/projects/ribica/front-ui/app/stores/navigationStore.js":[function(require,module,exports){ +var AppDispatcher = require('../dispatcher/appDispatcher'); +var EventEmitter = require('events').EventEmitter; +var Globals = require('../globals'); + +var NavigationConstants = require('../constants/navigationConstants') +var _ = require('underscore'); + + + + +var getGroupIdFromUrl = 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 = /grupa\/(\d+)\//g; + var match = itemIdRegex.exec(url); + + var result = Globals.ItemGroupIdOfStartPage; + if (match) { + result = match[1] + } + return result; +}; + +// Extend ItemStore with EventEmitter to add eventing capabilities +var NavigationStore = _.extend({}, EventEmitter.prototype, { + + getGroupIdFromUrl: getGroupIdFromUrl, + + // Emit Change event + emitChange: function() { + this.emit('change'); + }, + + // Add change listener + addChangeListener: function(callback) { + this.on('change', callback); + }, + + // Remove change listener + removeChangeListener: function(callback) { + this.removeListener('change', callback); + }, + + hideCart: function() { + // TODO: figure out how to find this out using Router + var url = document.URL; + var itemIdRegex = /\/(korpa|dostava)/g; + var match = itemIdRegex.exec(url); + if (match) { + return true; + } else { + return false; + } + } + +}); + + + + +// Register callback with AppDispatcher +NavigationStore.dispatchToken = AppDispatcher.register(function(payload) { + var action = payload.action; + + switch (action.actionType) { + + case NavigationConstants.CHANGE_URL: + var router = require('../router'); + setTimeout(function() { + router.transitionTo(action.url); + }, 0); + break; + + default: + return true; + } + + // If action was responded to, emit change event + NavigationStore.emitChange(); + return true; + +}); + +module.exports = NavigationStore; + +},{"../constants/navigationConstants":"/home/senadu/projects/ribica/front-ui/app/constants/navigationConstants.js","../dispatcher/appDispatcher":"/home/senadu/projects/ribica/front-ui/app/dispatcher/appDispatcher.js","../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","../router":"/home/senadu/projects/ribica/front-ui/app/router.js","events":"/home/senadu/projects/ribica/front-ui/node_modules/events/events.js","underscore":"/home/senadu/projects/ribica/front-ui/node_modules/underscore/underscore.js"}],"/home/senadu/projects/ribica/front-ui/app/stores/searchStore.js":[function(require,module,exports){ +var AppDispatcher = require('../dispatcher/appDispatcher'); +var EventEmitter = require('events').EventEmitter; + +var SearchConstants = require('../constants/searchConstants'); +var SearchActions = require('../actions/searchActions'); +var NavigationActions = require('../actions/navigationActions'); + +var ItemSearchCollection = require('../models/itemSearchCollection'); +var globals = require('../globals'); +var _ = require('underscore'); + +var _searchBoxState = { + q: '' +}; + +var _searchResultsState = { + q: '', + items: (new ItemSearchCollection()) +}; + +var handleSearchBoxChange = function(q) { + _searchBoxState.q = q; +}; + + +var handleGetSearchResults = function(q) { + _searchResultsState.q = q; + _searchBoxState.q = ''; + + var searchResults = new ItemSearchCollection(); + searchResults.setQuery(q); + searchResults.fetch({success: function() { + _searchResultsState.items = searchResults; + SearchStore.emit('change'); + }}); +}; + +// Extend ItemStore with EventEmitter to add eventing capabilities +var SearchStore = _.extend({}, EventEmitter.prototype, { + + getSearchBoxState: function() { + return _searchBoxState; + }, + getSearchResultsState: function() { + return _searchResultsState; + }, + // Emit Change event + emitChange: function() { + + 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 +SearchStore.dispatchToken = AppDispatcher.register(function(payload) { + var action = payload.action; + + switch(action.actionType) { + case SearchConstants.SEARCH_BOX_CHANGE: + handleSearchBoxChange(action.q); + break; + + case SearchConstants.GET_SEARCH_RESULTS: + handleGetSearchResults(action.q); + break; + default: + return true; + } + + // If action was responded to, emit change event + SearchStore.emitChange(); + return true; + +}); + +module.exports = SearchStore; + + +},{"../actions/navigationActions":"/home/senadu/projects/ribica/front-ui/app/actions/navigationActions.js","../actions/searchActions":"/home/senadu/projects/ribica/front-ui/app/actions/searchActions.js","../constants/searchConstants":"/home/senadu/projects/ribica/front-ui/app/constants/searchConstants.js","../dispatcher/appDispatcher":"/home/senadu/projects/ribica/front-ui/app/dispatcher/appDispatcher.js","../globals":"/home/senadu/projects/ribica/front-ui/app/globals.js","../models/itemSearchCollection":"/home/senadu/projects/ribica/front-ui/app/models/itemSearchCollection.js","events":"/home/senadu/projects/ribica/front-ui/node_modules/events/events.js","underscore":"/home/senadu/projects/ribica/front-ui/node_modules/underscore/underscore.js"}],"/home/senadu/projects/ribica/front-ui/app/stores/sectionStore.js":[function(require,module,exports){ +var AppDispatcher = require('../dispatcher/appDispatcher'); +var EventEmitter = require('events').EventEmitter; +var SectionCollection = require('../models/sectionCollection'); +var Section = require('../models/section'); +var SectionConstants = require('../constants/sectionConstants'); +var _ = require('underscore'); + +var _sectionDetails = new Section(); +var sectionState = { + sections : [], + hoveredSection : '' +}; + + +var loadSections = function() { + var sections = new SectionCollection(); + sections.fetch({success: function() { + sectionState.sections = sections.models; + // change will be called automatically when + // action is run but we need to emit it again + // when the data arive + // it's a bit "unfluxy" but convenient. + // "true philosophy" would be to run another "data arrived" action + SectionStore.emitChange(); + }}); +}; + +var loadSectionDetails = function(sectionId) { + var section = new Section({id : sectionId}); + section.fetch({ + success: function() { + _sectionDetails = section; + + SectionStore.emitChange(); + } + }); +}; + +var setHovered = function(id) { + sectionState.hoveredSection = id; +} + + +// Extend SectionStore with EventEmitter to add eventing capabilities +var SectionStore = _.extend({}, EventEmitter.prototype, { + + // Return Single Item With Details + getState: function() { + return sectionState; + }, + getSectionDetails: function() { + return _sectionDetails; + }, + // Emit Change event + emitChange: function() { + + 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) { + + // Respond to SELECT_ITEM action + case SectionConstants.LOAD_SECTIONS: + loadSections(); + break; + + case SectionConstants.SET_SECTION_HOVER: + setHovered(action.section.get('id')); + break; + + case SectionConstants.UNSET_SECTION_HOVER: + setHovered(''); + break; + + case SectionConstants.LOAD_SECTION_DETAILS: + loadSectionDetails(action.sectionId); + break; + + default: + return true; + } + + // If action was responded to, emit change event + SectionStore.emitChange(); + return true; + +}); + +module.exports = SectionStore; + + +},{"../constants/sectionConstants":"/home/senadu/projects/ribica/front-ui/app/constants/sectionConstants.js","../dispatcher/appDispatcher":"/home/senadu/projects/ribica/front-ui/app/dispatcher/appDispatcher.js","../models/section":"/home/senadu/projects/ribica/front-ui/app/models/section.js","../models/sectionCollection":"/home/senadu/projects/ribica/front-ui/app/models/sectionCollection.js","events":"/home/senadu/projects/ribica/front-ui/node_modules/events/events.js","underscore":"/home/senadu/projects/ribica/front-ui/node_modules/underscore/underscore.js"}],"/home/senadu/projects/ribica/front-ui/app/stores/userStore.js":[function(require,module,exports){ +var AppDispatcher = require('../dispatcher/appDispatcher'); +var EventEmitter = require('events').EventEmitter; +var UserConstants = require('../constants/userConstants'); +var _ = require('underscore'); +var CartActions = require('../actions/cartActions'); +var InitializationActions = require('../actions/initializationActions'); +var _registrationState = {}; +var _loginState = {}; + +var handleRegistrationSuccess = function(user) { + _registrationState = { + performed: true, + success: true + }; + + handleLoginSuccess(user); +}; + +var handleRegistrationFailure = function(error) { + _registrationState = { + performed: true, + success: false, + error: error + }; + +}; + +var handleLoginSuccess = function(user) { + _loginState = { + loggedIn: true, + user: user + }; + + refreshCart(); + +}; + +var handleLoginFailure = function(error) { + _loginState = { + loggedIn: false, + error: error + }; + +}; + +var handleCheckLoginArrived = function(user, error) { + if (user) { + _loginState = { + loggedIn: true, + user: user + }; + } else { + _loginState = { + loggedIn: false + }; + } +}; + +var handleLogoutDone = function() { + _registrationState = {}; + _loginState = { + loggedIn: false + } + + refreshCart(); +}; + +var refreshCart = function() { + + setTimeout(function() { + // needed for cart reset + InitializationActions.initialize(); + setTimeout(function() { + // reload the items + CartActions.load(); + }, 0); + }, 0); + +} + +// Extend SectionStore with EventEmitter to add eventing capabilities +var UserStore = _.extend({}, EventEmitter.prototype, { + + getRegistrationState: function() { + //return _categoryDetails; + return _registrationState; + }, + getLoginState: function() { + return _loginState; + }, + // Emit Change event + emitChange: function() { + 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 UserConstants.REGISTRATION_SUCCESS: + handleRegistrationSuccess(action.user); + break; + case UserConstants.REGISTRATION_FAILURE: + handleRegistrationFailure(action.error); + break; + case UserConstants.LOGIN_SUCCESS: + handleLoginSuccess(action.user); + break; + case UserConstants.LOGIN_FAILURE: + handleLoginFailure(action.error); + break; + case UserConstants.CHECK_LOGIN_ARRIVED: + handleCheckLoginArrived(action.user, action.error); + break; + case UserConstants.USER_LOGOUT_DONE: + handleLogoutDone(); + break; + default: + return true; + } + + // If action was responded to, emit change event + UserStore.emitChange(); + return true; + +}); + +module.exports = UserStore; + + +},{"../actions/cartActions":"/home/senadu/projects/ribica/front-ui/app/actions/cartActions.js","../actions/initializationActions":"/home/senadu/projects/ribica/front-ui/app/actions/initializationActions.js","../constants/userConstants":"/home/senadu/projects/ribica/front-ui/app/constants/userConstants.js","../dispatcher/appDispatcher":"/home/senadu/projects/ribica/front-ui/app/dispatcher/appDispatcher.js","events":"/home/senadu/projects/ribica/front-ui/node_modules/events/events.js","underscore":"/home/senadu/projects/ribica/front-ui/node_modules/underscore/underscore.js"}],"/home/senadu/projects/ribica/front-ui/app/utils/validation.js":[function(require,module,exports){ +var _ = require('underscore'); + +var Validations = { + _emailRe: /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, + isValidEmail: function(value) { + return this._emailRe.test(value); + + }, + isValidRequired: function(value) { + if (value === undefined || value === "") { + return false; + } + return true; + + }, + + safeString: function(item) { + if (!_.isString(item)) { + return ""; + } else { + return item; + } + + } + +}; + +module.exports = Validations; + +},{"underscore":"/home/senadu/projects/ribica/front-ui/node_modules/underscore/underscore.js"}],"/home/senadu/projects/ribica/front-ui/node_modules/Backbone.Mutators/backbone.mutators.js":[function(require,module,exports){ +/*! Backbone.Mutators - v0.4.4 +------------------------------ +Build @ 2015-02-03 +Documentation and Full License Available at: +http://asciidisco.github.com/Backbone.Mutators/index.html +git://github.com/asciidisco/Backbone.Mutators.git +Copyright (c) 2015 Sebastian Golasch + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the + +Software is furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE.*/ +(function (root, factory, undef) { + 'use strict'; + + if (typeof exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like enviroments that support module.exports, + // like Node. + module.exports = factory(require('underscore'), require('backbone')); + } else if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['underscore', 'backbone'], function (_, Backbone) { + // Check if we use the AMD branch of Back + _ = _ === undef ? root._ : _; + Backbone = Backbone === undef ? root.Backbone : Backbone; + return (root.returnExportsGlobal = factory(_, Backbone, root)); + }); + } else { + // Browser globals + root.returnExportsGlobal = factory(root._, root.Backbone); + } + +// Usage: +// +// Note: This plugin is UMD compatible, you can use it in node, amd and vanilla js envs +// +// Vanilla JS: +// +// +// +// +// Node: +// var _ = require('underscore'); +// var Backbone = require('backbone'); +// var Mutators = require('backbone.mutators'); +// +// +// AMD: +// define(['underscore', 'backbone', 'backbone.mutators'], function (_, Backbone, Mutators) { +// // insert sample from below +// return User; +// }); +// +// var User = Backbone.Model.extend({ +// mutators: { +// fullname: function () { +// return this.firstname + ' ' + this.lastname; +// } +// }, +// +// defaults: { +// firstname: 'Sebastian', +// lastname: 'Golasch' +// } +// }); +// +// var user = new User(); +// user.get('fullname') // returns 'Sebastian Golasch' +// user.toJSON() // return '{firstname: 'Sebastian', lastname: 'Golasch', fullname: 'Sebastian Golasch'}' + +}(this, function (_, Backbone, root, undef) { + 'use strict'; + + // check if we use the amd branch of backbone and underscore + Backbone = Backbone === undef ? root.Backbone : Backbone; + _ = _ === undef ? root._ : _; + + // extend backbones model prototype with the mutator functionality + var Mutator = function () {}, + oldGet = Backbone.Model.prototype.get, + oldSet = Backbone.Model.prototype.set, + oldToJson = Backbone.Model.prototype.toJSON; + + // This is necessary to ensure that Models declared without the mutators object do not throw and error + Mutator.prototype.mutators = {}; + + // override get functionality to fetch the mutator props + Mutator.prototype.get = function (attr) { + var isMutator = this.mutators !== undef; + + // check if we have a getter mutation + if (isMutator === true && _.isFunction(this.mutators[attr]) === true) { + return this.mutators[attr].call(this); + } + + // check if we have a deeper nested getter mutation + if (isMutator === true && _.isObject(this.mutators[attr]) === true && _.isFunction(this.mutators[attr].get) === true) { + return this.mutators[attr].get.call(this); + } + + return oldGet.call(this, attr); + }; + + // override set functionality to set the mutator props + Mutator.prototype.set = function (key, value, options) { + var isMutator = this.mutators !== undef, + ret = null, + attrs = null; + + ret = oldSet.call(this, key, value, options); + + // seamleassly stolen from backbone core + // check if the setter action is triggered + // using key <-> value or object + if (_.isObject(key) || key === null) { + attrs = key; + options = value; + } else { + attrs = {}; + attrs[key] = value; + } + + // check if we have a deeper nested setter mutation + if (isMutator === true && _.isObject(this.mutators[key]) === true) { + + // check if we need to set a single value + if (_.isFunction(this.mutators[key].set) === true) { + ret = this.mutators[key].set.call(this, key, attrs[key], options, _.bind(oldSet, this)); + } else if(_.isFunction(this.mutators[key])){ + ret = this.mutators[key].call(this, key, attrs[key], options, _.bind(oldSet, this)); + } + } + + if (isMutator === true && _.isObject(attrs)) { + _.each(attrs, _.bind(function (attr, attrKey) { + if (_.isObject(this.mutators[attrKey]) === true) { + // check if we need to set a single value + + var meth = this.mutators[attrKey]; + if(_.isFunction(meth.set)){ + meth = meth.set; + } + + if(_.isFunction(meth)){ + if (options === undef || (_.isObject(options) === true && options.silent !== true && (options.mutators !== undef && options.mutators.silent !== true))) { + this.trigger('mutators:set:' + attrKey); + } + meth.call(this, attrKey, attr, options, _.bind(oldSet, this)); + } + + } + }, this)); + } + + return ret; + }; + + // override toJSON functionality to serialize mutator properties + Mutator.prototype.toJSON = function (options) { + // fetch ye olde values + var attr = oldToJson.call(this), + isSaving, + isTransient; + // iterate over all mutators (if there are some) + _.each(this.mutators, _.bind(function (mutator, name) { + // check if we have some getter mutations + if (_.isObject(this.mutators[name]) === true && _.isFunction(this.mutators[name].get)) { + isSaving = (this.isSaving) ? this.isSaving(options, mutator, name) : _.has(options || {}, 'emulateHTTP'); + isTransient = this.mutators[name].transient; + if (!isSaving || !isTransient) { + attr[name] = _.bind(this.mutators[name].get, this)(); + } + } else if (_.isFunction(this.mutators[name])) { + attr[name] = _.bind(this.mutators[name], this)(); + } + }, this)); + + return attr; + }; + + // override get functionality to get HTML-escaped the mutator props + Mutator.prototype.escape = function (attr){ + var val = this.get(attr); + return _.escape(val == null ? '' : '' + val); + }; + + // extend the models prototype + _.extend(Backbone.Model.prototype, Mutator.prototype); + + // make mutators globally available under the Backbone namespace + Backbone.Mutators = Mutator; + return Mutator; +})); + +},{"backbone":"/home/senadu/projects/ribica/front-ui/node_modules/backbone/backbone.js","underscore":"/home/senadu/projects/ribica/front-ui/node_modules/Backbone.Mutators/node_modules/underscore/underscore.js"}],"/home/senadu/projects/ribica/front-ui/node_modules/Backbone.Mutators/node_modules/underscore/underscore.js":[function(require,module,exports){ +// Underscore.js 1.4.4 +// http://underscorejs.org +// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc. +// Underscore may be freely distributed under the MIT license. + +(function() { + + // Baseline setup + // -------------- + + // Establish the root object, `window` in the browser, or `global` on the server. + var root = this; + + // Save the previous value of the `_` variable. + var previousUnderscore = root._; + + // Establish the object that gets returned to break out of a loop iteration. + var breaker = {}; + + // Save bytes in the minified (but not gzipped) version: + var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; + + // Create quick reference variables for speed access to core prototypes. + var push = ArrayProto.push, + slice = ArrayProto.slice, + concat = ArrayProto.concat, + toString = ObjProto.toString, + hasOwnProperty = ObjProto.hasOwnProperty; + + // All **ECMAScript 5** native function implementations that we hope to use + // are declared here. + var + nativeForEach = ArrayProto.forEach, + nativeMap = ArrayProto.map, + nativeReduce = ArrayProto.reduce, + nativeReduceRight = ArrayProto.reduceRight, + nativeFilter = ArrayProto.filter, + nativeEvery = ArrayProto.every, + nativeSome = ArrayProto.some, + nativeIndexOf = ArrayProto.indexOf, + nativeLastIndexOf = ArrayProto.lastIndexOf, + nativeIsArray = Array.isArray, + nativeKeys = Object.keys, + nativeBind = FuncProto.bind; + + // Create a safe reference to the Underscore object for use below. + var _ = function(obj) { + if (obj instanceof _) return obj; + if (!(this instanceof _)) return new _(obj); + this._wrapped = obj; + }; + + // Export the Underscore object for **Node.js**, with + // backwards-compatibility for the old `require()` API. If we're in + // the browser, add `_` as a global object via a string identifier, + // for Closure Compiler "advanced" mode. + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = _; + } + exports._ = _; + } else { + root._ = _; + } + + // Current version. + _.VERSION = '1.4.4'; + + // Collection Functions + // -------------------- + + // The cornerstone, an `each` implementation, aka `forEach`. + // Handles objects with the built-in `forEach`, arrays, and raw objects. + // Delegates to **ECMAScript 5**'s native `forEach` if available. + var each = _.each = _.forEach = function(obj, iterator, context) { + if (obj == null) return; + if (nativeForEach && obj.forEach === nativeForEach) { + obj.forEach(iterator, context); + } else if (obj.length === +obj.length) { + for (var i = 0, l = obj.length; i < l; i++) { + if (iterator.call(context, obj[i], i, obj) === breaker) return; + } + } else { + for (var key in obj) { + if (_.has(obj, key)) { + if (iterator.call(context, obj[key], key, obj) === breaker) return; + } + } + } + }; + + // Return the results of applying the iterator to each element. + // Delegates to **ECMAScript 5**'s native `map` if available. + _.map = _.collect = function(obj, iterator, context) { + var results = []; + if (obj == null) return results; + if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); + each(obj, function(value, index, list) { + results[results.length] = iterator.call(context, value, index, list); + }); + return results; + }; + + var reduceError = 'Reduce of empty array with no initial value'; + + // **Reduce** builds up a single result from a list of values, aka `inject`, + // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. + _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { + var initial = arguments.length > 2; + if (obj == null) obj = []; + if (nativeReduce && obj.reduce === nativeReduce) { + if (context) iterator = _.bind(iterator, context); + return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); + } + each(obj, function(value, index, list) { + if (!initial) { + memo = value; + initial = true; + } else { + memo = iterator.call(context, memo, value, index, list); + } + }); + if (!initial) throw new TypeError(reduceError); + return memo; + }; + + // The right-associative version of reduce, also known as `foldr`. + // Delegates to **ECMAScript 5**'s native `reduceRight` if available. + _.reduceRight = _.foldr = function(obj, iterator, memo, context) { + var initial = arguments.length > 2; + if (obj == null) obj = []; + if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { + if (context) iterator = _.bind(iterator, context); + return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); + } + var length = obj.length; + if (length !== +length) { + var keys = _.keys(obj); + length = keys.length; + } + each(obj, function(value, index, list) { + index = keys ? keys[--length] : --length; + if (!initial) { + memo = obj[index]; + initial = true; + } else { + memo = iterator.call(context, memo, obj[index], index, list); + } + }); + if (!initial) throw new TypeError(reduceError); + return memo; + }; + + // Return the first value which passes a truth test. Aliased as `detect`. + _.find = _.detect = function(obj, iterator, context) { + var result; + any(obj, function(value, index, list) { + if (iterator.call(context, value, index, list)) { + result = value; + return true; + } + }); + return result; + }; + + // Return all the elements that pass a truth test. + // Delegates to **ECMAScript 5**'s native `filter` if available. + // Aliased as `select`. + _.filter = _.select = function(obj, iterator, context) { + var results = []; + if (obj == null) return results; + if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); + each(obj, function(value, index, list) { + if (iterator.call(context, value, index, list)) results[results.length] = value; + }); + return results; + }; + + // Return all the elements for which a truth test fails. + _.reject = function(obj, iterator, context) { + return _.filter(obj, function(value, index, list) { + return !iterator.call(context, value, index, list); + }, context); + }; + + // Determine whether all of the elements match a truth test. + // Delegates to **ECMAScript 5**'s native `every` if available. + // Aliased as `all`. + _.every = _.all = function(obj, iterator, context) { + iterator || (iterator = _.identity); + var result = true; + if (obj == null) return result; + if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); + each(obj, function(value, index, list) { + if (!(result = result && iterator.call(context, value, index, list))) return breaker; + }); + return !!result; + }; + + // Determine if at least one element in the object matches a truth test. + // Delegates to **ECMAScript 5**'s native `some` if available. + // Aliased as `any`. + var any = _.some = _.any = function(obj, iterator, context) { + iterator || (iterator = _.identity); + var result = false; + if (obj == null) return result; + if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); + each(obj, function(value, index, list) { + if (result || (result = iterator.call(context, value, index, list))) return breaker; + }); + return !!result; + }; + + // Determine if the array or object contains a given value (using `===`). + // Aliased as `include`. + _.contains = _.include = function(obj, target) { + if (obj == null) return false; + if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; + return any(obj, function(value) { + return value === target; + }); + }; + + // Invoke a method (with arguments) on every item in a collection. + _.invoke = function(obj, method) { + var args = slice.call(arguments, 2); + var isFunc = _.isFunction(method); + return _.map(obj, function(value) { + return (isFunc ? method : value[method]).apply(value, args); + }); + }; + + // Convenience version of a common use case of `map`: fetching a property. + _.pluck = function(obj, key) { + return _.map(obj, function(value){ return value[key]; }); + }; + + // Convenience version of a common use case of `filter`: selecting only objects + // containing specific `key:value` pairs. + _.where = function(obj, attrs, first) { + if (_.isEmpty(attrs)) return first ? null : []; + return _[first ? 'find' : 'filter'](obj, function(value) { + for (var key in attrs) { + if (attrs[key] !== value[key]) return false; + } + return true; + }); + }; + + // Convenience version of a common use case of `find`: getting the first object + // containing specific `key:value` pairs. + _.findWhere = function(obj, attrs) { + return _.where(obj, attrs, true); + }; + + // Return the maximum element or (element-based computation). + // Can't optimize arrays of integers longer than 65,535 elements. + // See: https://bugs.webkit.org/show_bug.cgi?id=80797 + _.max = function(obj, iterator, context) { + if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { + return Math.max.apply(Math, obj); + } + if (!iterator && _.isEmpty(obj)) return -Infinity; + var result = {computed : -Infinity, value: -Infinity}; + each(obj, function(value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; + computed >= result.computed && (result = {value : value, computed : computed}); + }); + return result.value; + }; + + // Return the minimum element (or element-based computation). + _.min = function(obj, iterator, context) { + if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { + return Math.min.apply(Math, obj); + } + if (!iterator && _.isEmpty(obj)) return Infinity; + var result = {computed : Infinity, value: Infinity}; + each(obj, function(value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; + computed < result.computed && (result = {value : value, computed : computed}); + }); + return result.value; + }; + + // Shuffle an array. + _.shuffle = function(obj) { + var rand; + var index = 0; + var shuffled = []; + each(obj, function(value) { + rand = _.random(index++); + shuffled[index - 1] = shuffled[rand]; + shuffled[rand] = value; + }); + return shuffled; + }; + + // An internal function to generate lookup iterators. + var lookupIterator = function(value) { + return _.isFunction(value) ? value : function(obj){ return obj[value]; }; + }; + + // Sort the object's values by a criterion produced by an iterator. + _.sortBy = function(obj, value, context) { + var iterator = lookupIterator(value); + return _.pluck(_.map(obj, function(value, index, list) { + return { + value : value, + index : index, + criteria : iterator.call(context, value, index, list) + }; + }).sort(function(left, right) { + var a = left.criteria; + var b = right.criteria; + if (a !== b) { + if (a > b || a === void 0) return 1; + if (a < b || b === void 0) return -1; + } + return left.index < right.index ? -1 : 1; + }), 'value'); + }; + + // An internal function used for aggregate "group by" operations. + var group = function(obj, value, context, behavior) { + var result = {}; + var iterator = lookupIterator(value || _.identity); + each(obj, function(value, index) { + var key = iterator.call(context, value, index, obj); + behavior(result, key, value); + }); + return result; + }; + + // Groups the object's values by a criterion. Pass either a string attribute + // to group by, or a function that returns the criterion. + _.groupBy = function(obj, value, context) { + return group(obj, value, context, function(result, key, value) { + (_.has(result, key) ? result[key] : (result[key] = [])).push(value); + }); + }; + + // Counts instances of an object that group by a certain criterion. Pass + // either a string attribute to count by, or a function that returns the + // criterion. + _.countBy = function(obj, value, context) { + return group(obj, value, context, function(result, key) { + if (!_.has(result, key)) result[key] = 0; + result[key]++; + }); + }; + + // Use a comparator function to figure out the smallest index at which + // an object should be inserted so as to maintain order. Uses binary search. + _.sortedIndex = function(array, obj, iterator, context) { + iterator = iterator == null ? _.identity : lookupIterator(iterator); + var value = iterator.call(context, obj); + var low = 0, high = array.length; + while (low < high) { + var mid = (low + high) >>> 1; + iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; + } + return low; + }; + + // Safely convert anything iterable into a real, live array. + _.toArray = function(obj) { + if (!obj) return []; + if (_.isArray(obj)) return slice.call(obj); + if (obj.length === +obj.length) return _.map(obj, _.identity); + return _.values(obj); + }; + + // Return the number of elements in an object. + _.size = function(obj) { + if (obj == null) return 0; + return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; + }; + + // Array Functions + // --------------- + + // Get the first element of an array. Passing **n** will return the first N + // values in the array. Aliased as `head` and `take`. The **guard** check + // allows it to work with `_.map`. + _.first = _.head = _.take = function(array, n, guard) { + if (array == null) return void 0; + return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; + }; + + // Returns everything but the last entry of the array. Especially useful on + // the arguments object. Passing **n** will return all the values in + // the array, excluding the last N. The **guard** check allows it to work with + // `_.map`. + _.initial = function(array, n, guard) { + return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); + }; + + // Get the last element of an array. Passing **n** will return the last N + // values in the array. The **guard** check allows it to work with `_.map`. + _.last = function(array, n, guard) { + if (array == null) return void 0; + if ((n != null) && !guard) { + return slice.call(array, Math.max(array.length - n, 0)); + } else { + return array[array.length - 1]; + } + }; + + // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. + // Especially useful on the arguments object. Passing an **n** will return + // the rest N values in the array. The **guard** + // check allows it to work with `_.map`. + _.rest = _.tail = _.drop = function(array, n, guard) { + return slice.call(array, (n == null) || guard ? 1 : n); + }; + + // Trim out all falsy values from an array. + _.compact = function(array) { + return _.filter(array, _.identity); + }; + + // Internal implementation of a recursive `flatten` function. + var flatten = function(input, shallow, output) { + each(input, function(value) { + if (_.isArray(value)) { + shallow ? push.apply(output, value) : flatten(value, shallow, output); + } else { + output.push(value); + } + }); + return output; + }; + + // Return a completely flattened version of an array. + _.flatten = function(array, shallow) { + return flatten(array, shallow, []); + }; + + // Return a version of the array that does not contain the specified value(s). + _.without = function(array) { + return _.difference(array, slice.call(arguments, 1)); + }; + + // Produce a duplicate-free version of the array. If the array has already + // been sorted, you have the option of using a faster algorithm. + // Aliased as `unique`. + _.uniq = _.unique = function(array, isSorted, iterator, context) { + if (_.isFunction(isSorted)) { + context = iterator; + iterator = isSorted; + isSorted = false; + } + var initial = iterator ? _.map(array, iterator, context) : array; + var results = []; + var seen = []; + each(initial, function(value, index) { + if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { + seen.push(value); + results.push(array[index]); + } + }); + return results; + }; + + // Produce an array that contains the union: each distinct element from all of + // the passed-in arrays. + _.union = function() { + return _.uniq(concat.apply(ArrayProto, arguments)); + }; + + // Produce an array that contains every item shared between all the + // passed-in arrays. + _.intersection = function(array) { + var rest = slice.call(arguments, 1); + return _.filter(_.uniq(array), function(item) { + return _.every(rest, function(other) { + return _.indexOf(other, item) >= 0; + }); + }); + }; + + // Take the difference between one array and a number of other arrays. + // Only the elements present in just the first array will remain. + _.difference = function(array) { + var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); + return _.filter(array, function(value){ return !_.contains(rest, value); }); + }; + + // Zip together multiple lists into a single array -- elements that share + // an index go together. + _.zip = function() { + var args = slice.call(arguments); + var length = _.max(_.pluck(args, 'length')); + var results = new Array(length); + for (var i = 0; i < length; i++) { + results[i] = _.pluck(args, "" + i); + } + return results; + }; + + // Converts lists into objects. Pass either a single array of `[key, value]` + // pairs, or two parallel arrays of the same length -- one of keys, and one of + // the corresponding values. + _.object = function(list, values) { + if (list == null) return {}; + var result = {}; + for (var i = 0, l = list.length; i < l; i++) { + if (values) { + result[list[i]] = values[i]; + } else { + result[list[i][0]] = list[i][1]; + } + } + return result; + }; + + // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), + // we need this function. Return the position of the first occurrence of an + // item in an array, or -1 if the item is not included in the array. + // Delegates to **ECMAScript 5**'s native `indexOf` if available. + // If the array is large and already in sort order, pass `true` + // for **isSorted** to use binary search. + _.indexOf = function(array, item, isSorted) { + if (array == null) return -1; + var i = 0, l = array.length; + if (isSorted) { + if (typeof isSorted == 'number') { + i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted); + } else { + i = _.sortedIndex(array, item); + return array[i] === item ? i : -1; + } + } + if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); + for (; i < l; i++) if (array[i] === item) return i; + return -1; + }; + + // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. + _.lastIndexOf = function(array, item, from) { + if (array == null) return -1; + var hasIndex = from != null; + if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { + return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); + } + var i = (hasIndex ? from : array.length); + while (i--) if (array[i] === item) return i; + return -1; + }; + + // Generate an integer Array containing an arithmetic progression. A port of + // the native Python `range()` function. See + // [the Python documentation](http://docs.python.org/library/functions.html#range). + _.range = function(start, stop, step) { + if (arguments.length <= 1) { + stop = start || 0; + start = 0; + } + step = arguments[2] || 1; + + var len = Math.max(Math.ceil((stop - start) / step), 0); + var idx = 0; + var range = new Array(len); + + while(idx < len) { + range[idx++] = start; + start += step; + } + + return range; + }; + + // Function (ahem) Functions + // ------------------ + + // Create a function bound to a given object (assigning `this`, and arguments, + // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if + // available. + _.bind = function(func, context) { + if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); + var args = slice.call(arguments, 2); + return function() { + return func.apply(context, args.concat(slice.call(arguments))); + }; + }; + + // Partially apply a function by creating a version that has had some of its + // arguments pre-filled, without changing its dynamic `this` context. + _.partial = function(func) { + var args = slice.call(arguments, 1); + return function() { + return func.apply(this, args.concat(slice.call(arguments))); + }; + }; + + // Bind all of an object's methods to that object. Useful for ensuring that + // all callbacks defined on an object belong to it. + _.bindAll = function(obj) { + var funcs = slice.call(arguments, 1); + if (funcs.length === 0) funcs = _.functions(obj); + each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); + return obj; + }; + + // Memoize an expensive function by storing its results. + _.memoize = function(func, hasher) { + var memo = {}; + hasher || (hasher = _.identity); + return function() { + var key = hasher.apply(this, arguments); + return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); + }; + }; + + // Delays a function for the given number of milliseconds, and then calls + // it with the arguments supplied. + _.delay = function(func, wait) { + var args = slice.call(arguments, 2); + return setTimeout(function(){ return func.apply(null, args); }, wait); + }; + + // Defers a function, scheduling it to run after the current call stack has + // cleared. + _.defer = function(func) { + return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); + }; + + // Returns a function, that, when invoked, will only be triggered at most once + // during a given window of time. + _.throttle = function(func, wait) { + var context, args, timeout, result; + var previous = 0; + var later = function() { + previous = new Date; + timeout = null; + result = func.apply(context, args); + }; + return function() { + var now = new Date; + var remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }; + + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // N milliseconds. If `immediate` is passed, trigger the function on the + // leading edge, instead of the trailing. + _.debounce = function(func, wait, immediate) { + var timeout, result; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + if (!immediate) result = func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) result = func.apply(context, args); + return result; + }; + }; + + // Returns a function that will be executed at most one time, no matter how + // often you call it. Useful for lazy initialization. + _.once = function(func) { + var ran = false, memo; + return function() { + if (ran) return memo; + ran = true; + memo = func.apply(this, arguments); + func = null; + return memo; + }; + }; + + // Returns the first function passed as an argument to the second, + // allowing you to adjust arguments, run code before and after, and + // conditionally execute the original function. + _.wrap = function(func, wrapper) { + return function() { + var args = [func]; + push.apply(args, arguments); + return wrapper.apply(this, args); + }; + }; + + // Returns a function that is the composition of a list of functions, each + // consuming the return value of the function that follows. + _.compose = function() { + var funcs = arguments; + return function() { + var args = arguments; + for (var i = funcs.length - 1; i >= 0; i--) { + args = [funcs[i].apply(this, args)]; + } + return args[0]; + }; + }; + + // Returns a function that will only be executed after being called N times. + _.after = function(times, func) { + if (times <= 0) return func(); + return function() { + if (--times < 1) { + return func.apply(this, arguments); + } + }; + }; + + // Object Functions + // ---------------- + + // Retrieve the names of an object's properties. + // Delegates to **ECMAScript 5**'s native `Object.keys` + _.keys = nativeKeys || function(obj) { + if (obj !== Object(obj)) throw new TypeError('Invalid object'); + var keys = []; + for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key; + return keys; + }; + + // Retrieve the values of an object's properties. + _.values = function(obj) { + var values = []; + for (var key in obj) if (_.has(obj, key)) values.push(obj[key]); + return values; + }; + + // Convert an object into a list of `[key, value]` pairs. + _.pairs = function(obj) { + var pairs = []; + for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]); + return pairs; + }; + + // Invert the keys and values of an object. The values must be serializable. + _.invert = function(obj) { + var result = {}; + for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key; + return result; + }; + + // Return a sorted list of the function names available on the object. + // Aliased as `methods` + _.functions = _.methods = function(obj) { + var names = []; + for (var key in obj) { + if (_.isFunction(obj[key])) names.push(key); + } + return names.sort(); + }; + + // Extend a given object with all the properties in passed-in object(s). + _.extend = function(obj) { + each(slice.call(arguments, 1), function(source) { + if (source) { + for (var prop in source) { + obj[prop] = source[prop]; + } + } + }); + return obj; + }; + + // Return a copy of the object only containing the whitelisted properties. + _.pick = function(obj) { + var copy = {}; + var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); + each(keys, function(key) { + if (key in obj) copy[key] = obj[key]; + }); + return copy; + }; + + // Return a copy of the object without the blacklisted properties. + _.omit = function(obj) { + var copy = {}; + var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); + for (var key in obj) { + if (!_.contains(keys, key)) copy[key] = obj[key]; + } + return copy; + }; + + // Fill in a given object with default properties. + _.defaults = function(obj) { + each(slice.call(arguments, 1), function(source) { + if (source) { + for (var prop in source) { + if (obj[prop] == null) obj[prop] = source[prop]; + } + } + }); + return obj; + }; + + // Create a (shallow-cloned) duplicate of an object. + _.clone = function(obj) { + if (!_.isObject(obj)) return obj; + return _.isArray(obj) ? obj.slice() : _.extend({}, obj); + }; + + // Invokes interceptor with the obj, and then returns obj. + // The primary purpose of this method is to "tap into" a method chain, in + // order to perform operations on intermediate results within the chain. + _.tap = function(obj, interceptor) { + interceptor(obj); + return obj; + }; + + // Internal recursive comparison function for `isEqual`. + var eq = function(a, b, aStack, bStack) { + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. + if (a === b) return a !== 0 || 1 / a == 1 / b; + // A strict comparison is necessary because `null == undefined`. + if (a == null || b == null) return a === b; + // Unwrap any wrapped objects. + if (a instanceof _) a = a._wrapped; + if (b instanceof _) b = b._wrapped; + // Compare `[[Class]]` names. + var className = toString.call(a); + if (className != toString.call(b)) return false; + switch (className) { + // Strings, numbers, dates, and booleans are compared by value. + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return a == String(b); + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for + // other numeric values. + return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a == +b; + // RegExps are compared by their source patterns and flags. + case '[object RegExp]': + return a.source == b.source && + a.global == b.global && + a.multiline == b.multiline && + a.ignoreCase == b.ignoreCase; + } + if (typeof a != 'object' || typeof b != 'object') return false; + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] == a) return bStack[length] == b; + } + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + var size = 0, result = true; + // Recursively compare objects and arrays. + if (className == '[object Array]') { + // Compare array lengths to determine if a deep comparison is necessary. + size = a.length; + result = size == b.length; + if (result) { + // Deep compare the contents, ignoring non-numeric properties. + while (size--) { + if (!(result = eq(a[size], b[size], aStack, bStack))) break; + } + } + } else { + // Objects with different constructors are not equivalent, but `Object`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && + _.isFunction(bCtor) && (bCtor instanceof bCtor))) { + return false; + } + // Deep compare objects. + for (var key in a) { + if (_.has(a, key)) { + // Count the expected number of properties. + size++; + // Deep compare each member. + if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; + } + } + // Ensure that both objects contain the same number of properties. + if (result) { + for (key in b) { + if (_.has(b, key) && !(size--)) break; + } + result = !size; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + return result; + }; + + // Perform a deep comparison to check if two objects are equal. + _.isEqual = function(a, b) { + return eq(a, b, [], []); + }; + + // Is a given array, string, or object empty? + // An "empty" object has no enumerable own-properties. + _.isEmpty = function(obj) { + if (obj == null) return true; + if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; + for (var key in obj) if (_.has(obj, key)) return false; + return true; + }; + + // Is a given value a DOM element? + _.isElement = function(obj) { + return !!(obj && obj.nodeType === 1); + }; + + // Is a given value an array? + // Delegates to ECMA5's native Array.isArray + _.isArray = nativeIsArray || function(obj) { + return toString.call(obj) == '[object Array]'; + }; + + // Is a given variable an object? + _.isObject = function(obj) { + return obj === Object(obj); + }; + + // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. + each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { + _['is' + name] = function(obj) { + return toString.call(obj) == '[object ' + name + ']'; + }; + }); + + // Define a fallback version of the method in browsers (ahem, IE), where + // there isn't any inspectable "Arguments" type. + if (!_.isArguments(arguments)) { + _.isArguments = function(obj) { + return !!(obj && _.has(obj, 'callee')); + }; + } + + // Optimize `isFunction` if appropriate. + if (typeof (/./) !== 'function') { + _.isFunction = function(obj) { + return typeof obj === 'function'; + }; + } + + // Is a given object a finite number? + _.isFinite = function(obj) { + return isFinite(obj) && !isNaN(parseFloat(obj)); + }; + + // Is the given value `NaN`? (NaN is the only number which does not equal itself). + _.isNaN = function(obj) { + return _.isNumber(obj) && obj != +obj; + }; + + // Is a given value a boolean? + _.isBoolean = function(obj) { + return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; + }; + + // Is a given value equal to null? + _.isNull = function(obj) { + return obj === null; + }; + + // Is a given variable undefined? + _.isUndefined = function(obj) { + return obj === void 0; + }; + + // Shortcut function for checking if an object has a given property directly + // on itself (in other words, not on a prototype). + _.has = function(obj, key) { + return hasOwnProperty.call(obj, key); + }; + + // Utility Functions + // ----------------- + + // Run Underscore.js in *noConflict* mode, returning the `_` variable to its + // previous owner. Returns a reference to the Underscore object. + _.noConflict = function() { + root._ = previousUnderscore; + return this; + }; + + // Keep the identity function around for default iterators. + _.identity = function(value) { + return value; + }; + + // Run a function **n** times. + _.times = function(n, iterator, context) { + var accum = Array(n); + for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); + return accum; + }; + + // Return a random integer between min and max (inclusive). + _.random = function(min, max) { + if (max == null) { + max = min; + min = 0; + } + return min + Math.floor(Math.random() * (max - min + 1)); + }; + + // List of HTML entities for escaping. + var entityMap = { + escape: { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/' + } + }; + entityMap.unescape = _.invert(entityMap.escape); + + // Regexes containing the keys and values listed immediately above. + var entityRegexes = { + escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), + unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') + }; + + // Functions for escaping and unescaping strings to/from HTML interpolation. + _.each(['escape', 'unescape'], function(method) { + _[method] = function(string) { + if (string == null) return ''; + return ('' + string).replace(entityRegexes[method], function(match) { + return entityMap[method][match]; + }); + }; + }); + + // If the value of the named property is a function then invoke it; + // otherwise, return it. + _.result = function(object, property) { + if (object == null) return null; + var value = object[property]; + return _.isFunction(value) ? value.call(object) : value; + }; + + // Add your own custom functions to the Underscore object. + _.mixin = function(obj) { + each(_.functions(obj), function(name){ + var func = _[name] = obj[name]; + _.prototype[name] = function() { + var args = [this._wrapped]; + push.apply(args, arguments); + return result.call(this, func.apply(_, args)); + }; + }); + }; + + // Generate a unique integer id (unique within the entire client session). + // Useful for temporary DOM ids. + var idCounter = 0; + _.uniqueId = function(prefix) { + var id = ++idCounter + ''; + return prefix ? prefix + id : id; + }; + + // By default, Underscore uses ERB-style template delimiters, change the + // following template settings to use alternative delimiters. + _.templateSettings = { + evaluate : /<%([\s\S]+?)%>/g, + interpolate : /<%=([\s\S]+?)%>/g, + escape : /<%-([\s\S]+?)%>/g + }; + + // When customizing `templateSettings`, if you don't want to define an + // interpolation, evaluation or escaping regex, we need one that is + // guaranteed not to match. + var noMatch = /(.)^/; + + // Certain characters need to be escaped so that they can be put into a + // string literal. + var escapes = { + "'": "'", + '\\': '\\', + '\r': 'r', + '\n': 'n', + '\t': 't', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; + + // JavaScript micro-templating, similar to John Resig's implementation. + // Underscore templating handles arbitrary delimiters, preserves whitespace, + // and correctly escapes quotes within interpolated code. + _.template = function(text, data, settings) { + var render; + settings = _.defaults({}, settings, _.templateSettings); + + // Combine delimiters into one regular expression via alternation. + var matcher = new RegExp([ + (settings.escape || noMatch).source, + (settings.interpolate || noMatch).source, + (settings.evaluate || noMatch).source + ].join('|') + '|$', 'g'); + + // Compile the template source, escaping string literals appropriately. + var index = 0; + var source = "__p+='"; + text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { + source += text.slice(index, offset) + .replace(escaper, function(match) { return '\\' + escapes[match]; }); + + if (escape) { + source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; + } + if (interpolate) { + source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; + } + if (evaluate) { + source += "';\n" + evaluate + "\n__p+='"; + } + index = offset + match.length; + return match; + }); + source += "';\n"; + + // If a variable is not specified, place data values in local scope. + if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; + + source = "var __t,__p='',__j=Array.prototype.join," + + "print=function(){__p+=__j.call(arguments,'');};\n" + + source + "return __p;\n"; + + try { + render = new Function(settings.variable || 'obj', '_', source); + } catch (e) { + e.source = source; + throw e; + } + + if (data) return render(data, _); + var template = function(data) { + return render.call(this, data, _); + }; + + // Provide the compiled function source as a convenience for precompilation. + template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; + + return template; + }; + + // Add a "chain" function, which will delegate to the wrapper. + _.chain = function(obj) { + return _(obj).chain(); + }; + + // OOP + // --------------- + // If Underscore is called as a function, it returns a wrapped object that + // can be used OO-style. This wrapper holds altered versions of all the + // underscore functions. Wrapped objects may be chained. + + // Helper function to continue chaining intermediate results. + var result = function(obj) { + return this._chain ? _(obj).chain() : obj; + }; + + // Add all of the Underscore functions to the wrapper object. + _.mixin(_); + + // Add all mutator Array functions to the wrapper. + each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + var obj = this._wrapped; + method.apply(obj, arguments); + if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; + return result.call(this, obj); + }; + }); + + // Add all accessor Array functions to the wrapper. + each(['concat', 'join', 'slice'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + return result.call(this, method.apply(this._wrapped, arguments)); + }; + }); + + _.extend(_.prototype, { + + // Start chaining a wrapped Underscore object. + chain: function() { + this._chain = true; + return this; + }, + + // Extracts the result from a wrapped and chained object. + value: function() { + return this._wrapped; + } + + }); + +}).call(this); + +},{}],"/home/senadu/projects/ribica/front-ui/node_modules/backbone/backbone.js":[function(require,module,exports){ +// Backbone.js 1.1.2 + +// (c) 2010-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// Backbone may be freely distributed under the MIT license. +// For all details and documentation: +// http://backbonejs.org + +(function(root, factory) { + + // Set up Backbone appropriately for the environment. Start with AMD. + if (typeof define === 'function' && define.amd) { + define(['underscore', 'jquery', 'exports'], function(_, $, exports) { + // Export global even in AMD case in case this script is loaded with + // others that may still expect a global Backbone. + root.Backbone = factory(root, exports, _, $); + }); + + // Next for Node.js or CommonJS. jQuery may not be needed as a module. + } else if (typeof exports !== 'undefined') { + var _ = require('underscore'); + factory(root, exports, _); + + // Finally, as a browser global. + } else { + root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$)); + } + +}(this, function(root, Backbone, _, $) { + + // Initial Setup + // ------------- + + // Save the previous value of the `Backbone` variable, so that it can be + // restored later on, if `noConflict` is used. + var previousBackbone = root.Backbone; + + // Create local references to array methods we'll want to use later. + var array = []; + var push = array.push; + var slice = array.slice; + var splice = array.splice; + + // Current version of the library. Keep in sync with `package.json`. + Backbone.VERSION = '1.1.2'; + + // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns + // the `$` variable. + Backbone.$ = $; + + // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable + // to its previous owner. Returns a reference to this Backbone object. + Backbone.noConflict = function() { + root.Backbone = previousBackbone; + return this; + }; + + // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option + // will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and + // set a `X-Http-Method-Override` header. + Backbone.emulateHTTP = false; + + // Turn on `emulateJSON` to support legacy servers that can't deal with direct + // `application/json` requests ... will encode the body as + // `application/x-www-form-urlencoded` instead and will send the model in a + // form param named `model`. + Backbone.emulateJSON = false; + + // Backbone.Events + // --------------- + + // A module that can be mixed in to *any object* in order to provide it with + // custom events. You may bind with `on` or remove with `off` callback + // functions to an event; `trigger`-ing an event fires all callbacks in + // succession. + // + // var object = {}; + // _.extend(object, Backbone.Events); + // object.on('expand', function(){ alert('expanded'); }); + // object.trigger('expand'); + // + var Events = Backbone.Events = { + + // Bind an event to a `callback` function. Passing `"all"` will bind + // the callback to all events fired. + on: function(name, callback, context) { + if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this; + this._events || (this._events = {}); + var events = this._events[name] || (this._events[name] = []); + events.push({callback: callback, context: context, ctx: context || this}); + return this; + }, + + // Bind an event to only be triggered a single time. After the first time + // the callback is invoked, it will be removed. + once: function(name, callback, context) { + if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this; + var self = this; + var once = _.once(function() { + self.off(name, once); + callback.apply(this, arguments); + }); + once._callback = callback; + return this.on(name, once, context); + }, + + // Remove one or many callbacks. If `context` is null, removes all + // callbacks with that function. If `callback` is null, removes all + // callbacks for the event. If `name` is null, removes all bound + // callbacks for all events. + off: function(name, callback, context) { + var retain, ev, events, names, i, l, j, k; + if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this; + if (!name && !callback && !context) { + this._events = void 0; + return this; + } + names = name ? [name] : _.keys(this._events); + for (i = 0, l = names.length; i < l; i++) { + name = names[i]; + if (events = this._events[name]) { + this._events[name] = retain = []; + if (callback || context) { + for (j = 0, k = events.length; j < k; j++) { + ev = events[j]; + if ((callback && callback !== ev.callback && callback !== ev.callback._callback) || + (context && context !== ev.context)) { + retain.push(ev); + } + } + } + if (!retain.length) delete this._events[name]; + } + } + + return this; + }, + + // Trigger one or many events, firing all bound callbacks. Callbacks are + // passed the same arguments as `trigger` is, apart from the event name + // (unless you're listening on `"all"`, which will cause your callback to + // receive the true name of the event as the first argument). + trigger: function(name) { + if (!this._events) return this; + var args = slice.call(arguments, 1); + if (!eventsApi(this, 'trigger', name, args)) return this; + var events = this._events[name]; + var allEvents = this._events.all; + if (events) triggerEvents(events, args); + if (allEvents) triggerEvents(allEvents, arguments); + return this; + }, + + // Tell this object to stop listening to either specific events ... or + // to every object it's currently listening to. + stopListening: function(obj, name, callback) { + var listeningTo = this._listeningTo; + if (!listeningTo) return this; + var remove = !name && !callback; + if (!callback && typeof name === 'object') callback = this; + if (obj) (listeningTo = {})[obj._listenId] = obj; + for (var id in listeningTo) { + obj = listeningTo[id]; + obj.off(name, callback, this); + if (remove || _.isEmpty(obj._events)) delete this._listeningTo[id]; + } + return this; + } + + }; + + // Regular expression used to split event strings. + var eventSplitter = /\s+/; + + // Implement fancy features of the Events API such as multiple event + // names `"change blur"` and jQuery-style event maps `{change: action}` + // in terms of the existing API. + var eventsApi = function(obj, action, name, rest) { + if (!name) return true; + + // Handle event maps. + if (typeof name === 'object') { + for (var key in name) { + obj[action].apply(obj, [key, name[key]].concat(rest)); + } + return false; + } + + // Handle space separated event names. + if (eventSplitter.test(name)) { + var names = name.split(eventSplitter); + for (var i = 0, l = names.length; i < l; i++) { + obj[action].apply(obj, [names[i]].concat(rest)); + } + return false; + } + + return true; + }; + + // A difficult-to-believe, but optimized internal dispatch function for + // triggering events. Tries to keep the usual cases speedy (most internal + // Backbone events have 3 arguments). + var triggerEvents = function(events, args) { + var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2]; + switch (args.length) { + case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return; + case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return; + case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return; + case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; + default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return; + } + }; + + var listenMethods = {listenTo: 'on', listenToOnce: 'once'}; + + // Inversion-of-control versions of `on` and `once`. Tell *this* object to + // listen to an event in another object ... keeping track of what it's + // listening to. + _.each(listenMethods, function(implementation, method) { + Events[method] = function(obj, name, callback) { + var listeningTo = this._listeningTo || (this._listeningTo = {}); + var id = obj._listenId || (obj._listenId = _.uniqueId('l')); + listeningTo[id] = obj; + if (!callback && typeof name === 'object') callback = this; + obj[implementation](name, callback, this); + return this; + }; + }); + + // Aliases for backwards compatibility. + Events.bind = Events.on; + Events.unbind = Events.off; + + // Allow the `Backbone` object to serve as a global event bus, for folks who + // want global "pubsub" in a convenient place. + _.extend(Backbone, Events); + + // Backbone.Model + // -------------- + + // Backbone **Models** are the basic data object in the framework -- + // frequently representing a row in a table in a database on your server. + // A discrete chunk of data and a bunch of useful, related methods for + // performing computations and transformations on that data. + + // Create a new model with the specified attributes. A client id (`cid`) + // is automatically generated and assigned for you. + var Model = Backbone.Model = function(attributes, options) { + var attrs = attributes || {}; + options || (options = {}); + this.cid = _.uniqueId('c'); + this.attributes = {}; + if (options.collection) this.collection = options.collection; + if (options.parse) attrs = this.parse(attrs, options) || {}; + attrs = _.defaults({}, attrs, _.result(this, 'defaults')); + this.set(attrs, options); + this.changed = {}; + this.initialize.apply(this, arguments); + }; + + // Attach all inheritable methods to the Model prototype. + _.extend(Model.prototype, Events, { + + // A hash of attributes whose current and previous value differ. + changed: null, + + // The value returned during the last failed validation. + validationError: null, + + // The default name for the JSON `id` attribute is `"id"`. MongoDB and + // CouchDB users may want to set this to `"_id"`. + idAttribute: 'id', + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // Return a copy of the model's `attributes` object. + toJSON: function(options) { + return _.clone(this.attributes); + }, + + // Proxy `Backbone.sync` by default -- but override this if you need + // custom syncing semantics for *this* particular model. + sync: function() { + return Backbone.sync.apply(this, arguments); + }, + + // Get the value of an attribute. + get: function(attr) { + return this.attributes[attr]; + }, + + // Get the HTML-escaped value of an attribute. + escape: function(attr) { + return _.escape(this.get(attr)); + }, + + // Returns `true` if the attribute contains a value that is not null + // or undefined. + has: function(attr) { + return this.get(attr) != null; + }, + + // Set a hash of model attributes on the object, firing `"change"`. This is + // the core primitive operation of a model, updating the data and notifying + // anyone who needs to know about the change in state. The heart of the beast. + set: function(key, val, options) { + var attr, attrs, unset, changes, silent, changing, prev, current; + if (key == null) return this; + + // Handle both `"key", value` and `{key: value}` -style arguments. + if (typeof key === 'object') { + attrs = key; + options = val; + } else { + (attrs = {})[key] = val; + } + + options || (options = {}); + + // Run validation. + if (!this._validate(attrs, options)) return false; + + // Extract attributes and options. + unset = options.unset; + silent = options.silent; + changes = []; + changing = this._changing; + this._changing = true; + + if (!changing) { + this._previousAttributes = _.clone(this.attributes); + this.changed = {}; + } + current = this.attributes, prev = this._previousAttributes; + + // Check for changes of `id`. + if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; + + // For each `set` attribute, update or delete the current value. + for (attr in attrs) { + val = attrs[attr]; + if (!_.isEqual(current[attr], val)) changes.push(attr); + if (!_.isEqual(prev[attr], val)) { + this.changed[attr] = val; + } else { + delete this.changed[attr]; + } + unset ? delete current[attr] : current[attr] = val; + } + + // Trigger all relevant attribute changes. + if (!silent) { + if (changes.length) this._pending = options; + for (var i = 0, l = changes.length; i < l; i++) { + this.trigger('change:' + changes[i], this, current[changes[i]], options); + } + } + + // You might be wondering why there's a `while` loop here. Changes can + // be recursively nested within `"change"` events. + if (changing) return this; + if (!silent) { + while (this._pending) { + options = this._pending; + this._pending = false; + this.trigger('change', this, options); + } + } + this._pending = false; + this._changing = false; + return this; + }, + + // Remove an attribute from the model, firing `"change"`. `unset` is a noop + // if the attribute doesn't exist. + unset: function(attr, options) { + return this.set(attr, void 0, _.extend({}, options, {unset: true})); + }, + + // Clear all attributes on the model, firing `"change"`. + clear: function(options) { + var attrs = {}; + for (var key in this.attributes) attrs[key] = void 0; + return this.set(attrs, _.extend({}, options, {unset: true})); + }, + + // Determine if the model has changed since the last `"change"` event. + // If you specify an attribute name, determine if that attribute has changed. + hasChanged: function(attr) { + if (attr == null) return !_.isEmpty(this.changed); + return _.has(this.changed, attr); + }, + + // Return an object containing all the attributes that have changed, or + // false if there are no changed attributes. Useful for determining what + // parts of a view need to be updated and/or what attributes need to be + // persisted to the server. Unset attributes will be set to undefined. + // You can also pass an attributes object to diff against the model, + // determining if there *would be* a change. + changedAttributes: function(diff) { + if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; + var val, changed = false; + var old = this._changing ? this._previousAttributes : this.attributes; + for (var attr in diff) { + if (_.isEqual(old[attr], (val = diff[attr]))) continue; + (changed || (changed = {}))[attr] = val; + } + return changed; + }, + + // Get the previous value of an attribute, recorded at the time the last + // `"change"` event was fired. + previous: function(attr) { + if (attr == null || !this._previousAttributes) return null; + return this._previousAttributes[attr]; + }, + + // Get all of the attributes of the model at the time of the previous + // `"change"` event. + previousAttributes: function() { + return _.clone(this._previousAttributes); + }, + + // Fetch the model from the server. If the server's representation of the + // model differs from its current attributes, they will be overridden, + // triggering a `"change"` event. + fetch: function(options) { + options = options ? _.clone(options) : {}; + if (options.parse === void 0) options.parse = true; + var model = this; + var success = options.success; + options.success = function(resp) { + if (!model.set(model.parse(resp, options), options)) return false; + if (success) success(model, resp, options); + model.trigger('sync', model, resp, options); + }; + wrapError(this, options); + return this.sync('read', this, options); + }, + + // Set a hash of model attributes, and sync the model to the server. + // If the server returns an attributes hash that differs, the model's + // state will be `set` again. + save: function(key, val, options) { + var attrs, method, xhr, attributes = this.attributes; + + // Handle both `"key", value` and `{key: value}` -style arguments. + if (key == null || typeof key === 'object') { + attrs = key; + options = val; + } else { + (attrs = {})[key] = val; + } + + options = _.extend({validate: true}, options); + + // If we're not waiting and attributes exist, save acts as + // `set(attr).save(null, opts)` with validation. Otherwise, check if + // the model will be valid when the attributes, if any, are set. + if (attrs && !options.wait) { + if (!this.set(attrs, options)) return false; + } else { + if (!this._validate(attrs, options)) return false; + } + + // Set temporary attributes if `{wait: true}`. + if (attrs && options.wait) { + this.attributes = _.extend({}, attributes, attrs); + } + + // After a successful server-side save, the client is (optionally) + // updated with the server-side state. + if (options.parse === void 0) options.parse = true; + var model = this; + var success = options.success; + options.success = function(resp) { + // Ensure attributes are restored during synchronous saves. + model.attributes = attributes; + var serverAttrs = model.parse(resp, options); + if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs); + if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) { + return false; + } + if (success) success(model, resp, options); + model.trigger('sync', model, resp, options); + }; + wrapError(this, options); + + method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update'); + if (method === 'patch') options.attrs = attrs; + xhr = this.sync(method, this, options); + + // Restore attributes. + if (attrs && options.wait) this.attributes = attributes; + + return xhr; + }, + + // Destroy this model on the server if it was already persisted. + // Optimistically removes the model from its collection, if it has one. + // If `wait: true` is passed, waits for the server to respond before removal. + destroy: function(options) { + options = options ? _.clone(options) : {}; + var model = this; + var success = options.success; + + var destroy = function() { + model.trigger('destroy', model, model.collection, options); + }; + + options.success = function(resp) { + if (options.wait || model.isNew()) destroy(); + if (success) success(model, resp, options); + if (!model.isNew()) model.trigger('sync', model, resp, options); + }; + + if (this.isNew()) { + options.success(); + return false; + } + wrapError(this, options); + + var xhr = this.sync('delete', this, options); + if (!options.wait) destroy(); + return xhr; + }, + + // Default URL for the model's representation on the server -- if you're + // using Backbone's restful methods, override this to change the endpoint + // that will be called. + url: function() { + var base = + _.result(this, 'urlRoot') || + _.result(this.collection, 'url') || + urlError(); + if (this.isNew()) return base; + return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(this.id); + }, + + // **parse** converts a response into the hash of attributes to be `set` on + // the model. The default implementation is just to pass the response along. + parse: function(resp, options) { + return resp; + }, + + // Create a new model with identical attributes to this one. + clone: function() { + return new this.constructor(this.attributes); + }, + + // A model is new if it has never been saved to the server, and lacks an id. + isNew: function() { + return !this.has(this.idAttribute); + }, + + // Check if the model is currently in a valid state. + isValid: function(options) { + return this._validate({}, _.extend(options || {}, { validate: true })); + }, + + // Run validation against the next complete set of model attributes, + // returning `true` if all is well. Otherwise, fire an `"invalid"` event. + _validate: function(attrs, options) { + if (!options.validate || !this.validate) return true; + attrs = _.extend({}, this.attributes, attrs); + var error = this.validationError = this.validate(attrs, options) || null; + if (!error) return true; + this.trigger('invalid', this, error, _.extend(options, {validationError: error})); + return false; + } + + }); + + // Underscore methods that we want to implement on the Model. + var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit']; + + // Mix in each Underscore method as a proxy to `Model#attributes`. + _.each(modelMethods, function(method) { + Model.prototype[method] = function() { + var args = slice.call(arguments); + args.unshift(this.attributes); + return _[method].apply(_, args); + }; + }); + + // Backbone.Collection + // ------------------- + + // If models tend to represent a single row of data, a Backbone Collection is + // more analagous to a table full of data ... or a small slice or page of that + // table, or a collection of rows that belong together for a particular reason + // -- all of the messages in this particular folder, all of the documents + // belonging to this particular author, and so on. Collections maintain + // indexes of their models, both in order, and for lookup by `id`. + + // Create a new **Collection**, perhaps to contain a specific type of `model`. + // If a `comparator` is specified, the Collection will maintain + // its models in sort order, as they're added and removed. + var Collection = Backbone.Collection = function(models, options) { + options || (options = {}); + if (options.model) this.model = options.model; + if (options.comparator !== void 0) this.comparator = options.comparator; + this._reset(); + this.initialize.apply(this, arguments); + if (models) this.reset(models, _.extend({silent: true}, options)); + }; + + // Default options for `Collection#set`. + var setOptions = {add: true, remove: true, merge: true}; + var addOptions = {add: true, remove: false}; + + // Define the Collection's inheritable methods. + _.extend(Collection.prototype, Events, { + + // The default model for a collection is just a **Backbone.Model**. + // This should be overridden in most cases. + model: Model, + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // The JSON representation of a Collection is an array of the + // models' attributes. + toJSON: function(options) { + return this.map(function(model){ return model.toJSON(options); }); + }, + + // Proxy `Backbone.sync` by default. + sync: function() { + return Backbone.sync.apply(this, arguments); + }, + + // Add a model, or list of models to the set. + add: function(models, options) { + return this.set(models, _.extend({merge: false}, options, addOptions)); + }, + + // Remove a model, or a list of models from the set. + remove: function(models, options) { + var singular = !_.isArray(models); + models = singular ? [models] : _.clone(models); + options || (options = {}); + var i, l, index, model; + for (i = 0, l = models.length; i < l; i++) { + model = models[i] = this.get(models[i]); + if (!model) continue; + delete this._byId[model.id]; + delete this._byId[model.cid]; + index = this.indexOf(model); + this.models.splice(index, 1); + this.length--; + if (!options.silent) { + options.index = index; + model.trigger('remove', model, this, options); + } + this._removeReference(model, options); + } + return singular ? models[0] : models; + }, + + // Update a collection by `set`-ing a new list of models, adding new ones, + // removing models that are no longer present, and merging models that + // already exist in the collection, as necessary. Similar to **Model#set**, + // the core operation for updating the data contained by the collection. + set: function(models, options) { + options = _.defaults({}, options, setOptions); + if (options.parse) models = this.parse(models, options); + var singular = !_.isArray(models); + models = singular ? (models ? [models] : []) : _.clone(models); + var i, l, id, model, attrs, existing, sort; + var at = options.at; + var targetModel = this.model; + var sortable = this.comparator && (at == null) && options.sort !== false; + var sortAttr = _.isString(this.comparator) ? this.comparator : null; + var toAdd = [], toRemove = [], modelMap = {}; + var add = options.add, merge = options.merge, remove = options.remove; + var order = !sortable && add && remove ? [] : false; + + // Turn bare objects into model references, and prevent invalid models + // from being added. + for (i = 0, l = models.length; i < l; i++) { + attrs = models[i] || {}; + if (attrs instanceof Model) { + id = model = attrs; + } else { + id = attrs[targetModel.prototype.idAttribute || 'id']; + } + + // If a duplicate is found, prevent it from being added and + // optionally merge it into the existing model. + if (existing = this.get(id)) { + if (remove) modelMap[existing.cid] = true; + if (merge) { + attrs = attrs === model ? model.attributes : attrs; + if (options.parse) attrs = existing.parse(attrs, options); + existing.set(attrs, options); + if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true; + } + models[i] = existing; + + // If this is a new, valid model, push it to the `toAdd` list. + } else if (add) { + model = models[i] = this._prepareModel(attrs, options); + if (!model) continue; + toAdd.push(model); + this._addReference(model, options); + } + + // Do not add multiple models with the same `id`. + model = existing || model; + if (order && (model.isNew() || !modelMap[model.id])) order.push(model); + modelMap[model.id] = true; + } + + // Remove nonexistent models if appropriate. + if (remove) { + for (i = 0, l = this.length; i < l; ++i) { + if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model); + } + if (toRemove.length) this.remove(toRemove, options); + } + + // See if sorting is needed, update `length` and splice in new models. + if (toAdd.length || (order && order.length)) { + if (sortable) sort = true; + this.length += toAdd.length; + if (at != null) { + for (i = 0, l = toAdd.length; i < l; i++) { + this.models.splice(at + i, 0, toAdd[i]); + } + } else { + if (order) this.models.length = 0; + var orderedModels = order || toAdd; + for (i = 0, l = orderedModels.length; i < l; i++) { + this.models.push(orderedModels[i]); + } + } + } + + // Silently sort the collection if appropriate. + if (sort) this.sort({silent: true}); + + // Unless silenced, it's time to fire all appropriate add/sort events. + if (!options.silent) { + for (i = 0, l = toAdd.length; i < l; i++) { + (model = toAdd[i]).trigger('add', model, this, options); + } + if (sort || (order && order.length)) this.trigger('sort', this, options); + } + + // Return the added (or merged) model (or models). + return singular ? models[0] : models; + }, + + // When you have more items than you want to add or remove individually, + // you can reset the entire set with a new list of models, without firing + // any granular `add` or `remove` events. Fires `reset` when finished. + // Useful for bulk operations and optimizations. + reset: function(models, options) { + options || (options = {}); + for (var i = 0, l = this.models.length; i < l; i++) { + this._removeReference(this.models[i], options); + } + options.previousModels = this.models; + this._reset(); + models = this.add(models, _.extend({silent: true}, options)); + if (!options.silent) this.trigger('reset', this, options); + return models; + }, + + // Add a model to the end of the collection. + push: function(model, options) { + return this.add(model, _.extend({at: this.length}, options)); + }, + + // Remove a model from the end of the collection. + pop: function(options) { + var model = this.at(this.length - 1); + this.remove(model, options); + return model; + }, + + // Add a model to the beginning of the collection. + unshift: function(model, options) { + return this.add(model, _.extend({at: 0}, options)); + }, + + // Remove a model from the beginning of the collection. + shift: function(options) { + var model = this.at(0); + this.remove(model, options); + return model; + }, + + // Slice out a sub-array of models from the collection. + slice: function() { + return slice.apply(this.models, arguments); + }, + + // Get a model from the set by id. + get: function(obj) { + if (obj == null) return void 0; + return this._byId[obj] || this._byId[obj.id] || this._byId[obj.cid]; + }, + + // Get the model at the given index. + at: function(index) { + return this.models[index]; + }, + + // Return models with matching attributes. Useful for simple cases of + // `filter`. + where: function(attrs, first) { + if (_.isEmpty(attrs)) return first ? void 0 : []; + return this[first ? 'find' : 'filter'](function(model) { + for (var key in attrs) { + if (attrs[key] !== model.get(key)) return false; + } + return true; + }); + }, + + // Return the first model with matching attributes. Useful for simple cases + // of `find`. + findWhere: function(attrs) { + return this.where(attrs, true); + }, + + // Force the collection to re-sort itself. You don't need to call this under + // normal circumstances, as the set will maintain sort order as each item + // is added. + sort: function(options) { + if (!this.comparator) throw new Error('Cannot sort a set without a comparator'); + options || (options = {}); + + // Run sort based on type of `comparator`. + if (_.isString(this.comparator) || this.comparator.length === 1) { + this.models = this.sortBy(this.comparator, this); + } else { + this.models.sort(_.bind(this.comparator, this)); + } + + if (!options.silent) this.trigger('sort', this, options); + return this; + }, + + // Pluck an attribute from each model in the collection. + pluck: function(attr) { + return _.invoke(this.models, 'get', attr); + }, + + // Fetch the default set of models for this collection, resetting the + // collection when they arrive. If `reset: true` is passed, the response + // data will be passed through the `reset` method instead of `set`. + fetch: function(options) { + options = options ? _.clone(options) : {}; + if (options.parse === void 0) options.parse = true; + var success = options.success; + var collection = this; + options.success = function(resp) { + var method = options.reset ? 'reset' : 'set'; + collection[method](resp, options); + if (success) success(collection, resp, options); + collection.trigger('sync', collection, resp, options); + }; + wrapError(this, options); + return this.sync('read', this, options); + }, + + // Create a new instance of a model in this collection. Add the model to the + // collection immediately, unless `wait: true` is passed, in which case we + // wait for the server to agree. + create: function(model, options) { + options = options ? _.clone(options) : {}; + if (!(model = this._prepareModel(model, options))) return false; + if (!options.wait) this.add(model, options); + var collection = this; + var success = options.success; + options.success = function(model, resp) { + if (options.wait) collection.add(model, options); + if (success) success(model, resp, options); + }; + model.save(null, options); + return model; + }, + + // **parse** converts a response into a list of models to be added to the + // collection. The default implementation is just to pass it through. + parse: function(resp, options) { + return resp; + }, + + // Create a new collection with an identical list of models as this one. + clone: function() { + return new this.constructor(this.models); + }, + + // Private method to reset all internal state. Called when the collection + // is first initialized or reset. + _reset: function() { + this.length = 0; + this.models = []; + this._byId = {}; + }, + + // Prepare a hash of attributes (or other model) to be added to this + // collection. + _prepareModel: function(attrs, options) { + if (attrs instanceof Model) return attrs; + options = options ? _.clone(options) : {}; + options.collection = this; + var model = new this.model(attrs, options); + if (!model.validationError) return model; + this.trigger('invalid', this, model.validationError, options); + return false; + }, + + // Internal method to create a model's ties to a collection. + _addReference: function(model, options) { + this._byId[model.cid] = model; + if (model.id != null) this._byId[model.id] = model; + if (!model.collection) model.collection = this; + model.on('all', this._onModelEvent, this); + }, + + // Internal method to sever a model's ties to a collection. + _removeReference: function(model, options) { + if (this === model.collection) delete model.collection; + model.off('all', this._onModelEvent, this); + }, + + // Internal method called every time a model in the set fires an event. + // Sets need to update their indexes when models change ids. All other + // events simply proxy through. "add" and "remove" events that originate + // in other collections are ignored. + _onModelEvent: function(event, model, collection, options) { + if ((event === 'add' || event === 'remove') && collection !== this) return; + if (event === 'destroy') this.remove(model, options); + if (model && event === 'change:' + model.idAttribute) { + delete this._byId[model.previous(model.idAttribute)]; + if (model.id != null) this._byId[model.id] = model; + } + this.trigger.apply(this, arguments); + } + + }); + + // Underscore methods that we want to implement on the Collection. + // 90% of the core usefulness of Backbone Collections is actually implemented + // right here: + var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl', + 'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select', + 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke', + 'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest', + 'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle', + 'lastIndexOf', 'isEmpty', 'chain', 'sample']; + + // Mix in each Underscore method as a proxy to `Collection#models`. + _.each(methods, function(method) { + Collection.prototype[method] = function() { + var args = slice.call(arguments); + args.unshift(this.models); + return _[method].apply(_, args); + }; + }); + + // Underscore methods that take a property name as an argument. + var attributeMethods = ['groupBy', 'countBy', 'sortBy', 'indexBy']; + + // Use attributes instead of properties. + _.each(attributeMethods, function(method) { + Collection.prototype[method] = function(value, context) { + var iterator = _.isFunction(value) ? value : function(model) { + return model.get(value); + }; + return _[method](this.models, iterator, context); + }; + }); + + // Backbone.View + // ------------- + + // Backbone Views are almost more convention than they are actual code. A View + // is simply a JavaScript object that represents a logical chunk of UI in the + // DOM. This might be a single item, an entire list, a sidebar or panel, or + // even the surrounding frame which wraps your whole app. Defining a chunk of + // UI as a **View** allows you to define your DOM events declaratively, without + // having to worry about render order ... and makes it easy for the view to + // react to specific changes in the state of your models. + + // Creating a Backbone.View creates its initial element outside of the DOM, + // if an existing element is not provided... + var View = Backbone.View = function(options) { + this.cid = _.uniqueId('view'); + options || (options = {}); + _.extend(this, _.pick(options, viewOptions)); + this._ensureElement(); + this.initialize.apply(this, arguments); + this.delegateEvents(); + }; + + // Cached regex to split keys for `delegate`. + var delegateEventSplitter = /^(\S+)\s*(.*)$/; + + // List of view options to be merged as properties. + var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events']; + + // Set up all inheritable **Backbone.View** properties and methods. + _.extend(View.prototype, Events, { + + // The default `tagName` of a View's element is `"div"`. + tagName: 'div', + + // jQuery delegate for element lookup, scoped to DOM elements within the + // current view. This should be preferred to global lookups where possible. + $: function(selector) { + return this.$el.find(selector); + }, + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // **render** is the core function that your view should override, in order + // to populate its element (`this.el`), with the appropriate HTML. The + // convention is for **render** to always return `this`. + render: function() { + return this; + }, + + // Remove this view by taking the element out of the DOM, and removing any + // applicable Backbone.Events listeners. + remove: function() { + this.$el.remove(); + this.stopListening(); + return this; + }, + + // Change the view's element (`this.el` property), including event + // re-delegation. + setElement: function(element, delegate) { + if (this.$el) this.undelegateEvents(); + this.$el = element instanceof Backbone.$ ? element : Backbone.$(element); + this.el = this.$el[0]; + if (delegate !== false) this.delegateEvents(); + return this; + }, + + // Set callbacks, where `this.events` is a hash of + // + // *{"event selector": "callback"}* + // + // { + // 'mousedown .title': 'edit', + // 'click .button': 'save', + // 'click .open': function(e) { ... } + // } + // + // pairs. Callbacks will be bound to the view, with `this` set properly. + // Uses event delegation for efficiency. + // Omitting the selector binds the event to `this.el`. + // This only works for delegate-able events: not `focus`, `blur`, and + // not `change`, `submit`, and `reset` in Internet Explorer. + delegateEvents: function(events) { + if (!(events || (events = _.result(this, 'events')))) return this; + this.undelegateEvents(); + for (var key in events) { + var method = events[key]; + if (!_.isFunction(method)) method = this[events[key]]; + if (!method) continue; + + var match = key.match(delegateEventSplitter); + var eventName = match[1], selector = match[2]; + method = _.bind(method, this); + eventName += '.delegateEvents' + this.cid; + if (selector === '') { + this.$el.on(eventName, method); + } else { + this.$el.on(eventName, selector, method); + } + } + return this; + }, + + // Clears all callbacks previously bound to the view with `delegateEvents`. + // You usually don't need to use this, but may wish to if you have multiple + // Backbone views attached to the same DOM element. + undelegateEvents: function() { + this.$el.off('.delegateEvents' + this.cid); + return this; + }, + + // Ensure that the View has a DOM element to render into. + // If `this.el` is a string, pass it through `$()`, take the first + // matching element, and re-assign it to `el`. Otherwise, create + // an element from the `id`, `className` and `tagName` properties. + _ensureElement: function() { + if (!this.el) { + var attrs = _.extend({}, _.result(this, 'attributes')); + if (this.id) attrs.id = _.result(this, 'id'); + if (this.className) attrs['class'] = _.result(this, 'className'); + var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs); + this.setElement($el, false); + } else { + this.setElement(_.result(this, 'el'), false); + } + } + + }); + + // Backbone.sync + // ------------- + + // Override this function to change the manner in which Backbone persists + // models to the server. You will be passed the type of request, and the + // model in question. By default, makes a RESTful Ajax request + // to the model's `url()`. Some possible customizations could be: + // + // * Use `setTimeout` to batch rapid-fire updates into a single request. + // * Send up the models as XML instead of JSON. + // * Persist models via WebSockets instead of Ajax. + // + // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests + // as `POST`, with a `_method` parameter containing the true HTTP method, + // as well as all requests with the body as `application/x-www-form-urlencoded` + // instead of `application/json` with the model in a param named `model`. + // Useful when interfacing with server-side languages like **PHP** that make + // it difficult to read the body of `PUT` requests. + Backbone.sync = function(method, model, options) { + var type = methodMap[method]; + + // Default options, unless specified. + _.defaults(options || (options = {}), { + emulateHTTP: Backbone.emulateHTTP, + emulateJSON: Backbone.emulateJSON + }); + + // Default JSON-request options. + var params = {type: type, dataType: 'json'}; + + // Ensure that we have a URL. + if (!options.url) { + params.url = _.result(model, 'url') || urlError(); + } + + // Ensure that we have the appropriate request data. + if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) { + params.contentType = 'application/json'; + params.data = JSON.stringify(options.attrs || model.toJSON(options)); + } + + // For older servers, emulate JSON by encoding the request into an HTML-form. + if (options.emulateJSON) { + params.contentType = 'application/x-www-form-urlencoded'; + params.data = params.data ? {model: params.data} : {}; + } + + // For older servers, emulate HTTP by mimicking the HTTP method with `_method` + // And an `X-HTTP-Method-Override` header. + if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) { + params.type = 'POST'; + if (options.emulateJSON) params.data._method = type; + var beforeSend = options.beforeSend; + options.beforeSend = function(xhr) { + xhr.setRequestHeader('X-HTTP-Method-Override', type); + if (beforeSend) return beforeSend.apply(this, arguments); + }; + } + + // Don't process data on a non-GET request. + if (params.type !== 'GET' && !options.emulateJSON) { + params.processData = false; + } + + // If we're sending a `PATCH` request, and we're in an old Internet Explorer + // that still has ActiveX enabled by default, override jQuery to use that + // for XHR instead. Remove this line when jQuery supports `PATCH` on IE8. + if (params.type === 'PATCH' && noXhrPatch) { + params.xhr = function() { + return new ActiveXObject("Microsoft.XMLHTTP"); + }; + } + + // Make the request, allowing the user to override any Ajax options. + var xhr = options.xhr = Backbone.ajax(_.extend(params, options)); + model.trigger('request', model, xhr, options); + return xhr; + }; + + var noXhrPatch = + typeof window !== 'undefined' && !!window.ActiveXObject && + !(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent); + + // Map from CRUD to HTTP for our default `Backbone.sync` implementation. + var methodMap = { + 'create': 'POST', + 'update': 'PUT', + 'patch': 'PATCH', + 'delete': 'DELETE', + 'read': 'GET' + }; + + // Set the default implementation of `Backbone.ajax` to proxy through to `$`. + // Override this if you'd like to use a different library. + Backbone.ajax = function() { + return Backbone.$.ajax.apply(Backbone.$, arguments); + }; + + // Backbone.Router + // --------------- + + // Routers map faux-URLs to actions, and fire events when routes are + // matched. Creating a new one sets its `routes` hash, if not set statically. + var Router = Backbone.Router = function(options) { + options || (options = {}); + if (options.routes) this.routes = options.routes; + this._bindRoutes(); + this.initialize.apply(this, arguments); + }; + + // Cached regular expressions for matching named param parts and splatted + // parts of route strings. + var optionalParam = /\((.*?)\)/g; + var namedParam = /(\(\?)?:\w+/g; + var splatParam = /\*\w+/g; + var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; + + // Set up all inheritable **Backbone.Router** properties and methods. + _.extend(Router.prototype, Events, { + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // Manually bind a single named route to a callback. For example: + // + // this.route('search/:query/p:num', 'search', function(query, num) { + // ... + // }); + // + route: function(route, name, callback) { + if (!_.isRegExp(route)) route = this._routeToRegExp(route); + if (_.isFunction(name)) { + callback = name; + name = ''; + } + if (!callback) callback = this[name]; + var router = this; + Backbone.history.route(route, function(fragment) { + var args = router._extractParameters(route, fragment); + router.execute(callback, args); + router.trigger.apply(router, ['route:' + name].concat(args)); + router.trigger('route', name, args); + Backbone.history.trigger('route', router, name, args); + }); + return this; + }, + + // Execute a route handler with the provided parameters. This is an + // excellent place to do pre-route setup or post-route cleanup. + execute: function(callback, args) { + if (callback) callback.apply(this, args); + }, + + // Simple proxy to `Backbone.history` to save a fragment into the history. + navigate: function(fragment, options) { + Backbone.history.navigate(fragment, options); + return this; + }, + + // Bind all defined routes to `Backbone.history`. We have to reverse the + // order of the routes here to support behavior where the most general + // routes can be defined at the bottom of the route map. + _bindRoutes: function() { + if (!this.routes) return; + this.routes = _.result(this, 'routes'); + var route, routes = _.keys(this.routes); + while ((route = routes.pop()) != null) { + this.route(route, this.routes[route]); + } + }, + + // Convert a route string into a regular expression, suitable for matching + // against the current location hash. + _routeToRegExp: function(route) { + route = route.replace(escapeRegExp, '\\$&') + .replace(optionalParam, '(?:$1)?') + .replace(namedParam, function(match, optional) { + return optional ? match : '([^/?]+)'; + }) + .replace(splatParam, '([^?]*?)'); + return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$'); + }, + + // Given a route, and a URL fragment that it matches, return the array of + // extracted decoded parameters. Empty or unmatched parameters will be + // treated as `null` to normalize cross-browser behavior. + _extractParameters: function(route, fragment) { + var params = route.exec(fragment).slice(1); + return _.map(params, function(param, i) { + // Don't decode the search params. + if (i === params.length - 1) return param || null; + return param ? decodeURIComponent(param) : null; + }); + } + + }); + + // Backbone.History + // ---------------- + + // Handles cross-browser history management, based on either + // [pushState](http://diveintohtml5.info/history.html) and real URLs, or + // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange) + // and URL fragments. If the browser supports neither (old IE, natch), + // falls back to polling. + var History = Backbone.History = function() { + this.handlers = []; + _.bindAll(this, 'checkUrl'); + + // Ensure that `History` can be used outside of the browser. + if (typeof window !== 'undefined') { + this.location = window.location; + this.history = window.history; + } + }; + + // Cached regex for stripping a leading hash/slash and trailing space. + var routeStripper = /^[#\/]|\s+$/g; + + // Cached regex for stripping leading and trailing slashes. + var rootStripper = /^\/+|\/+$/g; + + // Cached regex for detecting MSIE. + var isExplorer = /msie [\w.]+/; + + // Cached regex for removing a trailing slash. + var trailingSlash = /\/$/; + + // Cached regex for stripping urls of hash. + var pathStripper = /#.*$/; + + // Has the history handling already been started? + History.started = false; + + // Set up all inheritable **Backbone.History** properties and methods. + _.extend(History.prototype, Events, { + + // The default interval to poll for hash changes, if necessary, is + // twenty times a second. + interval: 50, + + // Are we at the app root? + atRoot: function() { + return this.location.pathname.replace(/[^\/]$/, '$&/') === this.root; + }, + + // Gets the true hash value. Cannot use location.hash directly due to bug + // in Firefox where location.hash will always be decoded. + getHash: function(window) { + var match = (window || this).location.href.match(/#(.*)$/); + return match ? match[1] : ''; + }, + + // Get the cross-browser normalized URL fragment, either from the URL, + // the hash, or the override. + getFragment: function(fragment, forcePushState) { + if (fragment == null) { + if (this._hasPushState || !this._wantsHashChange || forcePushState) { + fragment = decodeURI(this.location.pathname + this.location.search); + var root = this.root.replace(trailingSlash, ''); + if (!fragment.indexOf(root)) fragment = fragment.slice(root.length); + } else { + fragment = this.getHash(); + } + } + return fragment.replace(routeStripper, ''); + }, + + // Start the hash change handling, returning `true` if the current URL matches + // an existing route, and `false` otherwise. + start: function(options) { + if (History.started) throw new Error("Backbone.history has already been started"); + History.started = true; + + // Figure out the initial configuration. Do we need an iframe? + // Is pushState desired ... is it available? + this.options = _.extend({root: '/'}, this.options, options); + this.root = this.options.root; + this._wantsHashChange = this.options.hashChange !== false; + this._wantsPushState = !!this.options.pushState; + this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); + var fragment = this.getFragment(); + var docMode = document.documentMode; + var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7)); + + // Normalize root to always include a leading and trailing slash. + this.root = ('/' + this.root + '/').replace(rootStripper, '/'); + + if (oldIE && this._wantsHashChange) { + var frame = Backbone.$('