Image grid using React and JSON? - list

EDIT: I added this FIDDLE.
I'm a noobie to JSX and I'm trying to (correctly) build an image grid in React using a JSON file of images.
What is the best or "correct" approach for doing this?
My JSON file (updated per Nick's suggestion):
{
"images": [
{
"url": "./images/temp/playlist_tn_01.jpg",
"className": "playlist-tn"
},
{
"url": "./images/temp/playlist_tn_02.jpg",
"className": "playlist-tn"
},
{
"url": "./images/temp/playlist_tn_03.jpg",
"className": "playlist-tn"
}
]
}
My List module (copied from an example online):
var List = React.createClass({
render: function() {
return (
<ul>
{this.props.list.map(function(listValue){
return <li>{listValue}</li>;
})}
</ul>
)
}
});
module.exports = List;
My Updated list container component:
var Playlist = React.createClass({
render() {
let playlistImages = $.getJSON('images/temp/playlist_tn.json', images);
return (
<ReactCSSTransitionGroup transitionName="pagefade" transitionAppear={true} transitionAppearTimeout={500}>
<List list={playlistImages.images} />
</ReactCSSTransitionGroup>
)
}
})
module.exports = Playlist;
UPDATE FIDDLE

If you are rendering the component like this, e.g. passing the image node to the list property:
var mainComponent = React.createClass({
render() {
let jsonFileData = //ajax call to get file here
return (
<List list={jsonFileData.images} />
)
}
})
Then your jsx should read like this, you need to map the properties of the json onto an img element, and you need to use className instead of class as the property name :
var List = React.createClass({
render: function() {
return (
<ul>
{this.props.list.map(function(listValue){
return <li><img url={listValue.url} className={listValue.class} /></li>;
})}
</ul>
)
}
});
module.exports = List;
I would also recommend using arrow functions where possible in your maps, it makes things easier to read:
<ul>
{this.props.list.map( image =>
<li>
<img url={image.url} className={image.class} />
</li>
)}
</ul>
If you change your json class property to be called className and url to be src, you could use the spread operator to set all the props in one go too:
<img {...image} />
Working fiddle

Related

Props and Templates

I'm just creating my first custom component, and I'm really struggling with the basics. My component:
<template>
<StackLayout>
<Label :text="title" />
<Label :text="slate.description" />
</StackLayout>
</template>
<script>
var slate;
export default {
name: "SlateComponent",
props:
['slate', 'title'],
data() {
return {
slate: slate,
};
},
}
</script>
This component is to be updated regularly, and occupy a good chunk of the app home page:
<template>
<Page class="Page" actionBarHidden="true" backgroundSpanUnderStatusBar="true" >
<StackLayout>
<StackLayout row="0">
...
</StackLayout>
<StackLayout row="1">
<SlateComponent :title="title" :slate="slate" />
</StackLayout>
...
</Page>
</template>
<script>
...
import SlateComponent from "./SlateComponent";
var slateTitle;
var title;
var gameSlates;
var currentSlate;
var slate;
data() {
return {
events: events,
title: title,
slate: slate,
};
},
async created() {
this.gameSlates = await getGameSlates();
this.currentSlate = this.gameSlates[2];
this.title = this.currentSlate.description;
console.info("The title is: " + this.title);
this.slate = this.currentSlate;
}
};
Result: No matter what I do, no props object passes to the component.
If I comment out the
the app compiles and runs fine, logs currentSlate or its property, description and displays the component, including title.
But, when I include that line, it blows up, with the error: slate is undefined.
(I know that
props:
['slate', 'title'],
is not proper according to the style guide. But I couldn't get the preferred format to work either.)
What am I missing here?
When accessing props anywhere outside of the template, this is required
data() {
return {
slate: slate,
};
}
Should be
data() {
return {
slate: this.slate
};
},

Kendo Multi-select in cascading scenario unable to populate initial values

I'm using Telerik for MVC and trying to get the multi-select to populate with the initial values in an Edit scenario.
<script>
function filterProducts() {
return {
manufacturerId: $("#ServiceBulletinItem_ManufacturerId").val()
};
}
function onManufacturerChange(e) {
var v = e.sender.dataItem().Value;
$.post("#Url.Action("GetCascadeProducts", "Components")", { manufacturerId: v }, function (result) {
var grid = $("#ServiceBulletinItem_ApplicableProducts").data("kendoMultiSelect")
grid.setDataSource(result)
});
}
function InitialPopulate(manId) {
$.post("#Url.Action("GetCascadeProducts", "Components")", { manufacturerId: manId }, function (result) {
var grid = $("#ServiceBulletinItem_ApplicableProducts").data("kendoMultiSelect")
grid.setDataSource(result)
});
}
$(document).ready(function () {
$('.control-datepicker').Zebra_DatePicker();
var m = $("#ServiceBulletinItem_ManufacturerId").val();
InitialPopulate(m);
});
</script>
<div class="form-group">
#Html.LabelFor(m => m.ManufacturerList, "Manufacturer", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#(Html.Kendo().DropDownListFor(m => m.ServiceBulletinItem.ManufacturerId)
.HtmlAttributes(new { #class = "col-md-6 form-control" })
.Filter("contains")
.DataValueField("Value")
.DataTextField("Text")
.BindTo((IEnumerable<SelectListItem>)Model.ManufacturerSelectList)
.HtmlAttributes(new { style = "width:70%;" }).Events(e =>
{
e.Change("onManufacturerChange");
})
)
</div >
</div >
<div class="form-group">
#Html.LabelFor(m => m.ProductList, "Product", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#(Html.Kendo().MultiSelectFor(m => m.ServiceBulletinItem.ApplicableProducts)
.AutoClose(false)
.DataTextField("ProductName")
.DataValueField("ProductId")
.Placeholder("Select products...")
)
</div>
</div>
I'm trying to populate the manufacturer drop down and the Product multiSelect. The ApplicableProducts item is an IEnumerable representing the ProductId's of all those previously selected and I know that when I select the manufacturer and it calls the GetCascadeProducts controller method it will return back a collection of ProductId and ProductName for all the manufacturers products of which those productId is the ApplicableProducts property should exist.
On document.ready I can call the InitialPopulate method with the manufacturerID which will populate the multiSelect items but can't seem to populate the initial values.
I couldnt get the binding working correctly so ended up using
#(Html.Kendo().MultiSelect()
.Name("ServiceBulletinItem_ApplicableProducts")
.AutoClose(false)
.DataTextField("ProductName")
.DataValueField("ProductId")
.Placeholder("Select products 2...")
.AutoBind(false)
)
and then on the using the following code on document ready to make an ajax call to populate the manufacturer and product controls
function PopulateProductsInitial(manId) {
$.post("#Url.Action("GetCascadeProducts", "Components")", { manufacturerId: manId }, function (result) {
var grid = $("#ServiceBulletinItem_ApplicableProducts").data("kendoMultiSelect")
grid.setDataSource(result);
var s = $("#ServiceBulletinItem_Id").val();
$.post("#Url.Action("GetSBProducts", "ServiceBulletins")", { Id: s}, function (result) {
var arr = [];
result.forEach(function (element) {
arr.push(element.ProductId);
});
var grid = $("#ServiceBulletinItem_ApplicableProducts").data("kendoMultiSelect")
grid.value(arr);
});
});
}
}
$(document).ready(function () {
//Populate Initial Values
PopulateProductsInitial($("#ServiceBulletinItem_ManufacturerId").val());
$('#YourButton').click(SendForm);
});
The problem then became sending the selected items back to the controller when the edit was complete which again seemed convoluted because the control was not bound and therefore I had to make an Ajax call to submit the data.
function SendForm() {
var items = $("#ServiceBulletinItem_ApplicableProducts").data("kendoMultiSelect").value();
//Manipulate into ServiceBulletinViewModel for the save
var data = {
Id: $("#ServiceBulletinItem_Id").val(),
ServiceBulletinItem: {
Id: $("#ServiceBulletinItem_Id").val(),
ManufacturerId: $("#ServiceBulletinItem_ManufacturerId").val(),
IssueDate: $('#ServiceBulletinItem_IssueDate').val(),
Heading: $('#ServiceBulletinItem_Heading').val(),
Details: $('#ServiceBulletinItem_Details').val(),
Url: $('#ServiceBulletinItem_Url').val(),
SelectedProducts: items
}
}
$.ajax({
type: 'POST',
url: '/ServiceBulletins/Edit',
contentType: 'application/json',
data: JSON.stringify(data),
success: function (result) {
//Your success code here..
if (result.redirectUrl != null) {
window.location = result.redirectUrl;
}
},
error: function (jqXHR) {
if (jqXHR.status === 200) {
alert("Value Not found");
}
}
});
}
It all seemed a lot more convoluted than any of the demo's that teleriks and I couldnt find any good examples of binding from remote sources which looked similar.
As the binding is convention based I'm wondering if its possible to simplify the ajax calling for the post functionality with the correct naming of the controls so that I can simply get the selected items on the multiselect control or if the ajax post is the way to go.

ReactJS Transitions - Why doesn't this work?

I'd like to transition one element as it changes to another element.
I've got 3 examples:
one that works, but uses a list of items that are kept around (jsfiddle)
one that doesnt work, and only keeps one item around, depending on the state (jsfiddle)
another one that doesn't work, that keeps both items around and hides/shows them (jsfiddle using hide/show)
What I want is more like the second one, which is a very slight variation of the first attempt that works.
Option 1:
/** #jsx React.DOM */
var ReactTransitionGroup = React.addons.TransitionGroup;
var TodoList = React.createClass({
getInitialState: function() {
return {items: ['hello', 'world', 'click', 'me']};
},
handleAdd: function() {
var newItems =
this.state.items.concat([prompt('Enter some text')]);
this.setState({items: newItems});
},
handleRemove: function(i) {
var newItems = this.state.items;
newItems.splice(i, 1)
this.setState({items: newItems});
},
render: function() {
var items = this.state.items.map(function(item, i) {
return (
<div key={item} onClick={this.handleRemove.bind(this, i)}>
{item}
</div>
);
}.bind(this));
return (
<div>
<div><button onClick={this.handleAdd} /></div>
<ReactTransitionGroup transitionName="example">
{items}
</ReactTransitionGroup>
</div>
);
}
});
var app = React.renderComponent(<TodoList />, document.body);
Option 2:
JSX that doesn't work, but is closer to what I'd like to do (really, hide one view, and show another)
/** #jsx React.DOM */
var ReactTransitionGroup = React.addons.TransitionGroup;
var Test = React.createClass({
getInitialState: function() {
return {showOne:true}
},
onClick: function() {
this.setState({showOne:! this.state.showOne});
},
render: function() {
var result;
if (this.state.showOne)
{
result = <div ref="a">One</div>
}
else
{
result = <div ref="a">Two</div>
}
return (
<div>
<div><button onClick={this.onClick}>switch state</button></div>
<ReactTransitionGroup transitionName="example">
{result}
</ReactTransitionGroup>
</div>
);
}
});
var app = React.renderComponent(<Test />, document.body);
Option 3:
Uses hide/show to keep the 2 views around, but still doesn't work.
/** #jsx React.DOM */
var ReactTransitionGroup = React.addons.TransitionGroup;
var Test = React.createClass({
getInitialState: function() {
return {showOne:true}
},
onClick: function() {
this.setState({showOne:! this.state.showOne});
},
render: function() {
var result;
var c1 = this.state.showOne ? "hide" : "show";
var c2 = this.state.showOne ? "show" : "hide";
return (
<div>
<div><button onClick={this.onClick}>switch state</button></div>
<ReactTransitionGroup transitionName="example">
<div className={c1}>One</div>
<div className={c2}>Two</div>
</ReactTransitionGroup>
</div>
);
}
});
var app = React.renderComponent(<Test />, document.body);
So long story short - How can I make a transition execute on switching from one main "component" to another? I don't get why option 1 works, but option 2 doesn't!
React is just changing the content of the DOM because that's all that changed. Give the elements unique keys to make them animate.
if (this.state.showOne)
{
result = <div key="one">One</div>
}
else
{
result = <div key="two">Two</div>
}
JSFiddle
I used Michelle Treys answer to solve a similar problem using React-Router (1.0.1). Its not clear from the api that the key is needed. I was following React-routers suggestion to render a routes children in a parent as follows:
render() {
return (
<div id='app-wrapper'>
<ReactTransitionGroup component='div' className='transition-wrapper'>
{this.props.children}
</ReactTransitionGroup>
</div>
);
}
However the componentWillEnter only triggered on page load. Following Michelle's solution, I cloned a the children as per the react-router updates and added a key as follows:
render() {
const { location } = this.props;
return (
<div id='app-wrapper'>
<ReactTransitionGroup component='div' className='transition-wrapper'>
{React.cloneElement(this.props.children, {
key: location.pathname,
})}
</ReactTransitionGroup>
</div>
);
}
Thanks for the fix. Cheers

How do I give Marionette templates data attributes?

Here I am at the beginning of a project. I am using zurb-foundation and marionette. I have an element that is rendering a template that is supposed to be tabs. As it stands:
define([
"backbone",
"marionette"
], function(Backbone, Marionette) {
MyItem = Backbone.Marionette.ItemView.extend({
template: "#design-tabs",
className: "section-container tabs",
onRender: function() {
$(this.el).foundation();
}
});
return MyItem;
});
there are no tabs. I think this is because the <div> being rendered to replace the <script> tag in the template does not have a particular data attribute (data-section). I went looking for something like 'className' that I could add to the ItemView declaration above in order to include data-attributes, but I have come up dry. I want something like:
MyItem = Backbone.Marionette.ItemView.extend({
template: "#design-tabs",
data: {
data-section: "",
data-foo: "bar"
},
className: "section-container tabs",
.
.
.
How do I add data attributes to the <div> (or otherwise) that replaces the <script> in a template?
To add data properties, use Backbone's attributes hash:
var MyView = Backbone.Marionette.ItemView.extend({
template: "#design-tabs",
className: "section-container tabs",
attributes: {
"data-section": "",
"data-foo": "bar"
}
});
Documentation: http://backbonejs.org/#View-attributes
If you prefer or need dynamic values, you can do in this way:
attributes: function() {
return {
'src': this.model.get('avatar_src')
};
}

How to parse nested arrays in a model in emberjs

I'm having trouble figuring out how to parse some json data from our backend server.
To start of, here's the data it returns:
{
"newsitem": {
"id": "1",
"title": "Some title",
"images": [
"IMG_0147.JPG",
"desert1.jpg"
],
"videos": [
"AEOpX8tmiUI",
"kxopViU98Xo"
]
}
}
I'm trying to parse this in my model:
App.Newsitem = DS.Model.extend({
title: DS.attr('string'),
images: DS.attr('array'),
videos: DS.attr('array')
});
But this give me an error that 'array' is not supported. How should I go with parsing this data and how should I print out the values of images and videos in the DOM through a handlebars template? I'm looking for a best-practise answer.
A lot of credits to nerdyworm on the #emberjs channel for the answer: you have to create your own serialize/deserialize methods for your new data type like this:
DS.JSONTransforms.array = {
serialize: function(jsonData) {
return Ember.typeOf(jsonData) == 'array' ? jsonData : [];
},
deserialize: function(externalData) {
return Ember.typeOf(externalData) == 'array' ? externalData : [];
}
}
Then in your handlebars template you can do:
<script type="text/x-handlebars" data-template-name="newsitem">
<div class="detail">
{{#each image in images}}
{{image}}<br/>
{{/each}}
</div>
</script>