How to access children components props with react-rails - ruby-on-rails

I have watched this talk and I am right now trying to use react components to show a list of messages (in a chat). So I have two react components:
1) The "Message" component that is basicaly an li with the message props (dateTime,
userName, and body);
var Message = React.createClass({
render: function () {
return (
<li className="right clearfix">
<span className="chat-img pull-right">
<div className="chat-body clearfix">
<div className="header">
<small className=" text-muted"> sent {this.props.dateTime}
</small>
<strong className="primary-font">{this.props.userName</strong>
</div>
<p>{this.props.body}</p>
</div>
</li>
);
}
});
2) The "Messages" component which is a ul that lists all the chat messages
var Messages = React.createClass({
render() {
var createMessage = ({dateTime, userName, body, id}) => <Message key={id} dateTime={dateTime} userName={userName} body={body} />;
return <ul className="chat">{this.props.messages.map(createMessage)}</ul>;
}
});
In my view this is what I have:
<%= react_component('Messages', { messages: #messages }) %>
Nothing too complicated. The problem is that I don't know how to change the "message" component attributes. For example if I want to change the format of the messages created_at attributes ? Or the users names based on a model method ?
Do I have to parse these "props" with json from the controller ?
I'm just trying to figure out how react could work with my rails app, please don't hesitate to leave any suggestion/info. Thanks in advance

Related

Creating react components from array data

I am creating a fairly simple stats page for a dashboard in my rails application. I am using react-rails to create react components in my app and have one for a stat card.
For these cards I have created a very simple react component
import React from "react";
import PropTypes from "prop-types";
class StatCard extends React.Component {
render() {
const { title, data } = this.props;
return (
<React.Fragment>
<div className="card">
<div className="card-body">
<div className="row align-items-center">
<div className="col">
<h6 className="card-title text-uppercase text-muted mb-2">
{title}
</h6>
<span className="h2 mb-0">{data}</span>
</div>
<div className="col-auto">
<span className="h2 fe fe-briefcase text-muted mb-0" />
</div>
</div>
</div>
</div>
</React.Fragment>
);
}
}
StatCard.propTypes = {
title: PropTypes.string,
data: PropTypes.integer
};
export default StatCard;
In my html page right now I have 4 different components listed by calling this 4 different types with different data and a different title.
<%= react_component("StatCard", {title: 'Total users', data: #users.count }) %>
Would the best practice be to list an array of objects with this data listed and iterate over that and generate those components that way? If so, would this be data be best in the model or the controller?
The data structure might look something like this.
data = [
{ title: 'Total users', data: #users.count },
{ title: 'Total questions', data: #questions.count }
]
It is a good practice to have array of objects and iterate through that to return multiple similar components. you are using instance of user to get the count so it would be fine if you have them in controller

React/Rails error - A valid React element (or null) must be returned

I'm building a very simple blog app with React on the front-end and Rails on the backend. I can't quite figure out how to handle this error:
Uncaught Error: Posts.render(): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.
I have two react components, Post and Posts. Both are extremely simple. Here is Posts:
var Posts = React.createClass({
getInitialState: function(){
return {
posts: this.props.posts
}
},
render: function(){
var that = this;
var postBoard = this.state.posts.map(function(post){
return (
<Post post={post} key={post.id} />
);
});
return (
{ postBoard }
);
}
});
Here is Post:
var Post = React.createClass({
getInitialState: function(){
return {
post: this.props.post
}
},
render: function(){
return (
<div class="col-md-12">
<h3>{this.state.post.title}</h3>
<p>{this.state.post.content}</p>
</div>
);
}
});
Here is the Rails index view for Posts. I am using the react-rails gem:
<div class="container-fluid">
<div class="row">
<div class="col-md-6 col-md-push-3">
<h1>Posts</h1>
<%= react_component( "Posts", { posts: #posts }) %>
<br>
<%= link_to 'New Post', new_post_path %>
</div>
</div>
</div>
Here is the index action in my Posts controller:
def index
#posts = Post.all
end
I can't figure out what is causing this. It's been a few weeks since I've used React. I am clearly rusty. Help!
Wrap postBoard inside div. The reason of error is return methods return only one element. In your case postBoard is array. If the length of postBoard array is equal to 1, it should work without wraping it inside div element
return (
<div>
{ postBoard }
</div>
);

TypeError: Cannot set property 'chat_room_id' of undefined when using ng-if

I'm running into a bit of a road block here.
I'm working on a chat feature, which is currently within a rails partial in the application.html.erb file.
What I'm looking to do is have a list of a user's friends display in the chat area initially. When the user clicks on a friend's name, the corresponding chat room opens and the messages between the user and friend are displayed. If the user wishes to exit the chat, and view his friends list again, the user would simply click a button (currently "View Friends").
I am currently toggling between friends and rooms/messages using the ng-if directive.
I have not completely set this up yet, so I know there are bugs. I have created user friendships within rails, set up my REST API in rails, and can GET and POST resources via Angular and Restangular.
However, the issue at hand is that ever since I implemented the ng-if directives in the partial, I am getting the following error:
TypeError: Cannot set property 'chat_room_id' of undefined
If I am to remove the ng-if directives and all of the corresponding $scope.messagesVisible and $scope.friendsVisible variable references within the controller, the form submit works, so I know it has something to do with my implementation of the ng-if directives, and the fact that newMessage is undefined, but I'm not sure why.
I suspect it has something to do with Angular promises or my lack of understanding in regards to both Angular promises and the ng-if directive, but if anyone could shed some light on why this may be happening (and offer a solution) that would be amazing.
Thanks, guys!
Code below (please excuse terrible styling - it's a placeholder)
rails_partial:
<section ng-app="atmosphere" ng-controller="AtmoChatCtrl" style="position:relative; top:32%; float:right; right:5px; margin-bottom:10px; margin-top:30px;">
<div ng-if="friendsVisible" style="position:relative; left:35px; width:100px;">
<% user_friendships.each do |friendship| %>
<ul>
<% friend = friendship.friend %>
<% if friendship.accepted? %>
<li style="cursor:pointer;" ng-click="setChatAttributes(<%= friend.id %>, <%= current_user.id %> );"><%= friend.name %></li>
</ul>
<% end %>
<% end %>
</div>
<div ng-if="messagesVisible" style="position:absolute; top:60%; width: 150px; height:40%; right:-80px; margin-top:100px">
<div>
<p ng-repeat="message in messages">{{message.user_id}}: {{message.body}}</p>
<form ng-submit="saveMessage();">
<input type="text" ng-model="newMessage.body">
</form>
</div>
</div>
<div style="position:absolute; top:300px;">
<button ng-if="messagesVisible" ng-click="viewFriends();">View Friends</button>
</div>
</section>
<%= javascript_include_tag "angular/application" %>
<%= javascript_include_tag "http://ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular-resource.min.js" %>
angular_controller:
angular.module('AtmoChatCtrl', [])
.controller('AtmoChatCtrl', ['$scope', '$resource', '$interval','Pusher','Restangular', function ($scope, $resource, $interval, Pusher, Restangular) {
$scope.baseMessages = Restangular.one('api/chat_rooms', 2).all('chat_messages');
$scope.roomId = '2';
$scope.messagesVisible = false;
$scope.friendsVisible = true;
console.log("SET ROOM");
$interval(function(){
$scope.baseMessages.getList().then(function(messages) {
$scope.messages = messages;
});
console.log("POLLING");
}, 1000);
$scope.saveMessage = function() {
$scope.newMessage.chat_room_id = $scope.roomId;
$scope.newMessage.user_id = $scope.userId;
$scope.baseMessages.post($scope.newMessage).then(function(newMessage){
$scope.messages.push(newMessage);
console.log("SAVED");
})
}
$scope.setChatAttributes = function(roomId, userId) {
$scope.baseMessages = Restangular.one('api/chat_rooms', roomId).all('chat_messages');
$scope.roomId = roomId;
$scope.userId = userId;
$scope.messagesVisible = true;
$scope.friendsVisible = false;
console.log(roomId)
console.log(userId)
}
$scope.viewFriends = function() {
$scope.friendsVisible = true;
$scope.messagesVisible = false;
}
}]);
NVM. I'm an idiot. Solved by setting $scope.newMessage = {}; after the first $scope.friendsVisible = true;

Cannot get jqueryui tabs to work properly in Ember view

I'm trying to run up a little prototype in Ember.JS at the moment with a view to completely re-writing the UI of a web application as an Ember Application running against a WebAPI, but although I've managed to get Ember running OK, I cannot get jqueryui to initialise the tabs correctly.
It seems to work fine if within the view I put static data for tabs to be created from, but if I'm using dynamic data then it just doesn't work.
I have an Ember view template
<script type="text/x-handlebars" id="index">
<div id="tabs" class="ui-tabs">
<ul>
{{#each model}}
<li>
<span class="ui-icon ui-icon-person"></span>
<a {{bindAttr href="route"}} {{bindAttr title="tabTitle"}}><span>{{title}}</span></a>
</li>
{{/each}}
</ul>
{{#each model}}
<div {{bindAttr id="tabTitle"}}>
<p>
Retrieving Data - {{title}}
</p>
</div>
{{/each}}
</div>
</script>
and a view
App.IndexView = Ember.View.extend({
templateName: 'index',
didInsertElement: function () {
var tabs = $("#tabs").tabs();
}
});
and a model
App.Section = DS.Model.extend({
name: DS.attr('string'),
title: DS.attr('string'),
tabTitle: function () {
return 'tab-' + this.get('name');
}.property("name"),
route: function () {
return '#' + this.get('tabTitle');
}.property("tabTitle")
});
App.Section.FIXTURES = [
{
id: 1,
name: 'home',
title: 'Home'
},
{
id: 2,
name: 'users',
title: 'Users'
}
];
It appears to generate the HTML correctly (from checking in Firebug), but this does not work, where as if I replace the template with
<script type="text/x-handlebars" id="index">
<div id="tabs" class="ui-tabs">
<ul>
<li>
<span class="ui-icon ui-icon-person"></span>
<span>Home</span>
</li>
<li>
<span class="ui-icon ui-icon-person"></span>
<span>Users</span>
</li>
</ul>
<div id="tab-home">
<p>
Retrieving Data - Home
</p>
</div>
<div id="tab-users">
<p>
Retrieving Data - Users
</p>
</div>
</div>
</script>
it works perfectly.
I'm assuming that it's something to do with the DOM not being completely rendered by the time the tabs are initialised, but everything I can find says that didInsertElement is the place to do it, and I have had time to dig deeper yet.
I'd be grateful for any ideas.
Edit: I've managed to make this work in a fashion by doing the following:
App.IndexView = Ember.View.extend({
templateName: 'index',
didInsertElement: function () {
Ember.run.next(this, function () {
if (this.$('#tab-users').length > 0) {
var tabs = $('#tabs').tabs();
} else {
Ember.run.next(this.didInsertElement);
}
});
},
});
The problem with this is that 1) it requires me to know what one of the last elements that will be written to the view is called (and obviously with dynamic data I won't necessarily know that), so that I can keep checking for it, and 2) the inefficiency of this technique makes me want to scream!
In addition, we get a good old FoUC (Flash of Unstyled Content) after things have been rendered, but before we then get JQueryUI to style them correctly.
Any suggestions gratefully received.
It's still not nice... but this at least does work, and is reasonably efficient...
From Ember.js - Using a Handlebars helper to detect that a subview has rendered I discovered how to write a trigger, and because of the way that the run loop seems to work, inserting the trigger in the last loop on the page causes it to be called n times, but only after the loop is complete, so a quick state check "hasBeenTriggered" ensures that you only execute the delgate function once.
My code now looks like this:
<script type="text/x-handlebars" id="index">
<div id="tabs" class="ui-tabs">
<ul>
{{#each model}}
<li>
<span class="ui-icon ui-icon-person"></span>
<a {{bindAttr href="route"}} {{bindAttr title="tabTitle"}}><span>{{title}}</span></a>
</li>
{{/each}}
</ul>
{{#each model}}
<div {{bindAttr id="tabTitle"}}>
<p>
Retrieving Data - {{title}}
</p>
</div>
{{trigger "triggered"}}
{{/each}}
</div>
</script>
with the trigger
Ember.Handlebars.registerHelper('trigger', function (evtName, options) {
options = arguments[arguments.length - 1];
var hash = options.hash,
view = options.data.view,
target;
view = view.get('concreteView');
if (hash.target) {
target = Ember.Handlebars.get(this, hash.target, options);
} else {
target = view;
}
Ember.run.next(function () {
target.trigger(evtName);
});
});
and view
App.IndexView = Ember.View.extend({
templateName: 'index',
hasBeenTriggered: false,
triggered: function () {
if (!this.get("hasBeenTriggered")) {
var tabs = $('#tabs').tabs();
this.set("hasBeenTriggered", true);
}
}
});
I'd love to know if there's a better way of doing this, as this still doesn't get round the FOUC problem either (which again can be done with more JS hacks)... :(

How to get the value of the element selected in ListView JQUERYMOBILE

Hi i'm developping a simple listView that lists the column "firstname" of my table : i want to get the selected value (name) , i found this link but it shows he how to get the index and not the value of the selected item http://jsfiddle.net/w2JZU/
here's my code :
HTML:
<div id="popup-bg">
</div>
<div id="popup-box">
<div data-role="page" id="home">
<div data-role="header">
<h1>Players</h1>
</div>
<div data-role="content">
<ul data-role="listview" id="artiste" >
</ul>
</div>
</div>
</div>
</section>
js :
function successCB()
{
db.transaction(queryDB, errorCB);
}
function queryDB(tx)
{
tx.executeSql('SELECT * FROM Players ', [], querySuccess, errorCB);
}
function querySuccess(tx, results)
{
var len = results.rows.length;
var dataset= results.rows;
$("#artiste").empty();
for (var i = 0; i < len; i++)
{ item = dataset.item(i);
$("#artiste").append( "<li data-theme='c'><a href='game.html'>
<img src='images/avatar.jpg'><h3>"+item['firstName']+"</h3></a></li>" );
}
There isn't a "value" for an item in a list using the li tag. However, you can get the text of what's in that list element using the jQuery .text() method. I've modified the jsfiddle you referenced to do exactly that: http://jsfiddle.net/Cht2e/
You might want to consider adding another attribute to the li tag, such as data-name (you can make up the attribute) and then you can get that via the jQuery .attr() method. For example, you might change you append code to do:
$("#artiste").append( "<li data-theme='c' data-name='"+item['firstName']+"'><a href='game.html'>
<img src='images/avatar.jpg'><h3>"+item['firstName']+"</h3></a></li>" );
And then attach your click handler like this:
$('#artiste').children('li').on('click', function () {
alert('Selected Name=' + $(this).attr('data-name'));
});
I don't think this is necessarily the best structure or approach to take, but it will accomplish what you're asking.

Resources