Simple Ajax request, that loops over data in React.js - javascript

New to react and not 100% on how I should approach this relatively simple problem.
I'm currently looking to gather some images from Reddit, that push those images back to the 'pImage' state.
Then have those said images display within the 'content' div. Usually, I would just go about this with a for loop, but is there a special way I should be processing it with react?
componentDidMount: function() {
var self = this;
$.get(this.props.source, function(result) {
var collection = result.data.children;
if (this.isMounted()) {
this.setState({
//Should I put a for loop in here? Or something else?
pImage: collection.data.thumbnail
});
}
}.bind(this));
}
Fiddle to show my current state: https://jsfiddle.net/69z2wepo/2327/

Here is how you would do it with a map function in the render method:
var ImageCollect = React.createClass({
getInitialState: function() {
return {
pImage: []
};
},
componentDidMount: function() {
var self = this;
$.get(this.props.source, function(result) {
var collection = result.data.children;
if (this.isMounted()) {
this.setState({
pImage: collection
});
}
}.bind(this));
},
render: function() {
images = this.state.pImage || [];
return (
<div>
Images:
{images.map(function(image){
return <img src={image.data.thumbnail}/>
})}
</div>
);
}
});
React.render(
<ImageCollect source="https://www.reddit.com/r/pics/top/.json" />,
document.getElementById('content')
);
Here is working fiddle: http://jsfiddle.net/2ftzw6xd/

Related

Toggle class on mouse click event

I've got a Backbone.View that renders a collection and filters it on mouse click. I need to add class active to the button that I click, but the problem is that buttons are the part of this view and whenever I try to addClass or toggleClass it just renders again with default class. Here's my view:
var ResumeList = Backbone.View.extend({
events: {
'click #active': 'showActive',
'click #passed': 'showPassed'
},
initialize: function () {
this.collection = new ResumeCollection();
},
render: function (filtered) {
var self = this;
var data;
if (!filtered) {
data = this.collection.toArray();
} else {
data = filtered.toArray();
}
this.$el.html(this.template({ collection: this.collection.toJSON() });
_.each(data, function (cv) {
self.$el.append((new ResumeView({model: cv})).render().$el);
});
return this;
},
showActive: function () {
this.$('#active').toggleClass('active');
// a function that returns a new filtered collection
var filtered = this.collection.filterActive();
this.render(filtered);
}
});
But as I've already told, the class I need is toggled or added just for a moment, then the view is rendered again and it is set to default class. Is there any way to handle this?
I simplified the rendering and added some optimizations.
Since we don't have your template, I changed it to enable optimization:
<button id="active" type="button">Active</button>
<button id="passed" type="button">Passed</button>
<div class="list"></div>
Then your list view could be like this:
var ResumeList = Backbone.View.extend({
events: {
'click #active': 'showActive',
'click #passed': 'showPassed'
},
initialize: function() {
this.childViews = [];
this.collection = new ResumeCollection();
},
render: function() {
this.$el.html(this.template());
// cache the jQuery element once
this.elem = {
$list: this.$('.list'),
$active: this.$('#active'),
$passed: this.$('#passed')
};
this.renderList(); // default list rendering
return this;
},
renderList: function(collection) {
this.elem.$list.empty();
this.removeChildren();
collection = collection || this.collection.models;
// Underscore's 'each' has a argument for the context.
_.each(collection, this.renderItem, this);
},
renderItem: function(model) {
var view = new ResumeView({ model: model });
this.childViews.push(view);
this.elem.$list.append(view.render().el);
},
showActive: function() {
this.elem.$active.toggleClass('active');
var filtered = this.collection.filterActive();
this.renderList(filtered);
},
/**
* Gracefully call remove for each child view.
* This is to avoid memory leaks with listeners.
*/
removeChildren: function() {
var view;
while ((view = this.childViews.pop())) {
view.remove();
}
},
});
Additional information:
Managing Views and Memory Leaks
Underscore's each (notice the third argument)
Try to avoid callback hell, make the callbacks reusable (like renderItem)
I have edited the snippet can you try this.
var ResumeList = Backbone.View.extend({
events: {
'click #active': 'filterActive',
'click #passed': 'showPassed'
},
toggleElement: undefined,
initialize: function () {
this.collection = new ResumeCollection();
},
render: function (filtered) {
var self = this;
var data;
if (!filtered) {
data = this.collection.toArray();
} else {
data = filtered.toArray();
}
this.$el.html(this.template({ collection: this.collection.toJSON() });
_.each(data, function (cv) {
self.$el.append((new ResumeView({model: cv})).render().$el);
});
return this;
},
filterActive: function (evt) {
this.toggleElement = this.$el.find(evt.currentTarget);
// a function that returns a new filtered collection
var filtered = this.collection.filterActive();
this.render(filtered);
this.toggleActive();
},
toggleActive: function() {
if(this.toggleElement.is(':checked')) {
this.$el.find('#active').addClass('active');
} else {
this.$el.find('#active').removeClass('active');
}
}
});
Please note: I have taken checkbox element instead of button.

Rendering the view returns undefined

I've got a collection view with two filter methods, and a render method which takes a parameter. The problem I'm stuck with is that when rendering the view for the first time it returns me an error. Here's my collection:
var ResumeCollection = Backbone.Collection.extend({
url: 'http://localhost:3000',
filterActive: function () {
var active = this.where({interviewed: false});
return new ResumeCollection(active);
},
filterInterviewed: function () {
var interviewed = this.where({interviewed: true});
return new ResumeCollection(interviewed);
}
});
And my view:
var ResumeList = Backbone.View.extend({
events { // hash array of filter events },
initialize: function () {
this.collection.fetch();
},
render: function (filtered) {
var self = this;
var data;
if (!filtered) {
data = this.collection.toArray();
} else {
data = filtered.toArray();
}
_.each(data, function (cv) {
self.$el.append((new ResumeView({model: cv})).render().$el);
});
return this;
},
showActive: function (ev) {
var filtered = this.collection.filterActive();
this.render(filtered);
},
showInterviewed: function (ev) {
var filtered = this.collection.filterInterviewed();
this.render(filtered);
},
showAll: function (ev) {
this.render(this.collection);
}
});
This view gets rendered for the first time in my router by passing a collection:
var AppRouter = Backbone.Router.extend({
routes: {
'': 'home'
},
initialize: function () {
this.layout = new LayoutView();
}
home: function () {
this.layout.render(new ResumeList({
collection: new ResumeCollection()
}));
}
});
And this is the layout view within which all the other views are rendered:
var LayoutView = Backbone.View.extend({
el: $('#outlet'),
render: function (view) {
if (this.child && this.child !== view) {
this.child.undelegateEvents();
}
this.child = view;
this.child.setElement(this.$el).render();
return this;
}
});
When I just refresh my page, I get filtered.toArray is not a function error and nothing is rendered respectively. After inspecting everything in the debugger, I found out that when the view gets rendered for the first time, the filtered attribute receives an empty collection, assigns it to data variable, which becomes an empty array and goes to the body of render function, becoming undefined after that. The mysteries go here: whenever I click items, that are bound to my show* events, they act exactly as expected and render either models where interviewed === false, or true or the whole collection. This looks kinda magic to me and I haven't got the faintest idea what can I do with that.
ADDED: GitHub repo with this project
Your home function on the AppRouter has a typo. You have an extra semi-colon.
home: function () {
this.layout.render(new ResumeList({
collection: new ResumeCollection();
}));
}
Should be
home: function () {
this.layout.render(new ResumeList({
collection: new ResumeCollection()
}));
}
I needed to remove it to get the JSFiddle working: https://jsfiddle.net/4gyne5ev/1/
I'd recommend adding some kind of linting tool into your IDE or Build process (http://eslint.org/)
You need to add home url content to your db.json file like this
"" : [
{
'somthing': 'somthing'
}
]
After a piece of advice from my mentor I realized that the core of the problem was in asynchronous origin of fetch method -- as I passed this.collection.fetch in my initialize function, it executed after my render method, not before it, so my render method had just nothing to render when the view was called for the first time. So, this fix worked:
var ResumeList = Backbone.View.extend({
initialize: function (options) {
this.collection = options.collection();
// removed .fetch() method from here
},
render: function (filtered) {
var self = this;
var data;
// and added it here:
this.collection.fetch({
success: function (collection) {
if (!filtered) {
data = collection.toArray();
} else {
data = filtered.toArray();
}
self.$el.html(self.template(collection.toJSON()));
_.each(data, function (cv) {
self.$el.append((new ResumeView({model: cv})).render().$el);
})
}
});
}
});
And this worked perfectly and exactly as I needed.

Map function doesn't iterate all children

i am building a sidebar for an app, which consists of smaller elements. Right now stuck with an issue where sidebar child element array isn't fully iterated although each child element consists of an unique key. I have a feeling that it might be related to key. :/
Sidebar element component:
var AppSidebar = React.createClass({
getInitialState: function() {
return {
children: []
};
},
getPlaylists: function() {
var that = this;
// returns array of objects
spotify.getMePlaylists(function(err, playlists) {
if(err)
console.error(err);
that.setState({
children: playlists
});
});
},
componentDidMount: function() {
this.getPlaylists();
},
render: function() {
// iterates all children
this.state.children.map(function(playlist) {
console.log(1, playlist.id, playlist.name);
});
var playlists = this.state.children;
return (
<div id="sidebar">
playlists.map(function(playlist) {
// only returns first playlist id and name
console.log(2, playlist.id, playlist.name);
return (<AppSidebarElement key={playlist.id} {...playlist} />);
})}
</div>
);
}
});
Sidebar component:
var AppSidebarElement = React.createClass({
render: function() {
return (
<div className="playlist">
{this.props.name}
</div>
);
}
});
Console output:
1 "75dwLdmL07hDEDWqX17QeE" "The Indie Mix"
1 "2wOXCZ6fJAjpekKlnbg49F" "Interesting artists"
1 "6ULfvJbsdzF7u14dBZo9w1" "chsshhh"
1 "4LJ5hkgqt04IKw454SUJqV" "Motivational Songs"
1 "75if3ukZz2tPS60IVMPhw6" "Best of the Suits Soundtrack"
...
2 "69H6RgTVs1jrv1IuuLe1a5" "Deep Dark Indie"
Issue was caused by code which i cleaned up while posting it to stack-overflow, it was related to calling wrong this within map function context.

EmberJS: Refreshing a model?

Hello again everyone.
EDIT: I want to emphasize that I can find no docs on the solution for this.
I am using a route to perform a search query to my server. The server does all the data logic and such and returns a list of objects that match the keywords given. I am taking those results and feeding them to the model so that I can use the {{#each}} helper to iterate over each result.
The problem I am having is that the model does not want to refresh when the searchText (search input) changes. I've tried several things. I'm not worried about creating too many ajax requests as my server performs the search query in 2ms. Here's what I have now.
App.SearchView = Ember.View.extend({...
EDIT:
Thank you for the answer.
App.SearchView = Ember.View.extend({
didInsertElement: function () {
this._super();
Ember.run.scheduleOnce('afterRender', this, this.focusSearch);
},
focusSearch: function () {
$(".searchInput").focus().val(this.get("controller").get('searchTextI'));
}
});
App.SearchRoute = Ember.Route.extend({
model: function () {
return this.controllerFor('search').processSearch();
}
});
App.SearchController = Ember.ArrayController.extend({
searchTextI: null,
timeoutid: null,
processid: null,
updateSearch: function () {
if(this.get('timeoutid')) {clearTimeout(this.get('timeoutid')); }
var i = this.get('searchTextI');
var sc = this;
clearTimeout(this.get('processid'));
this.controllerFor('index').set('searchText', i); //set the search text on transition
if(i.length < 3) {
this.set('timeoutid', setTimeout(function () {
sc.controllerFor('index').set("transitioningFromSearch", true);
sc.transitionToRoute('index');
}, 1500));
} else {
var self = this;
this.set('processid', setTimeout(function() {
self.processSearch().then(function(result) {
self.set('content', result);
});
}, 1000));
}
}.observes('searchTextI'),
processSearch: function () {
return $.getJSON('http://api.*********/search', { 'token': guestToken, 'search_query': this.get('searchTextI') }).then(function(data) { if(data == "No Results Found.") { return []; } else { return data; } }).fail(function() { return ["ERROR."]; });
}
});
Don't observe anything within a route and don't define any computed properties. Routes are not the place for these. Apart from that, the model doesn't fire because controller is undefined.
One way to achieve what you want:
App.SearchRoute = Ember.Route.extend({
model: function () {
this.controllerFor('search').searchQuery();
}.observes('controller.searchText') //not triggering an ajax request...
});
App.SearchController = Ember.ArrayController.extend({
searchQuery: function() {
return $.getJSON('http://api.**************/search', { 'token': guestToken, 'search_query': t }).fail(function() {
return null; //prevent error substate.
});
}
onSearchTextChange: function() {
var controller = this;
this.searchQuery().then(function(result) {
controller.set('content', result);
});
}.observes('searchText')
});
Putting an observes on the model hook is not going to do anything. You should simply do what you were thinking of doing and say
processSearch: function () {
this.set('content', $.getJSON....);
}

Why are my jQuery hide events not firing and my Backbone sub view not rendering?

Now Solved - See bottom....
I've got a Backbone list view with a button on it that should show the edit elements.
Neither the jQuery hide() call in the 'showAddEntry' function or the view rendering for 'versionEditView' are doing anything at all. I've stepped right through and I'm not getting any errors. I've even tried manually running methods in the console to see what's going on with hide, but I'm not getting anywhere.
Here's the main view...
define(['ministry', 'jquery', 'models/m-version-info', 'views/about/v-edit-version-info-entry', 'text!templates/version-info/version-info.html'],
function(Ministry, $, VersionInfo, VersionInfoEditView, TemplateSource) {
var versionInfoEntriesView = Ministry.View.extend({
el: '#mainAppArea',
template: Handlebars.compile(TemplateSource),
versionInfoEditView: null,
initialize: function () {
this.$addEntryArea = $('#addVersionInfoEntryArea');
this.$addEntryButton = $('#addVersionInfoEntryButton');
},
events: {
'click #addVersionInfoEntryButton': 'showAddEntry'
},
render: function () {
var that = this;
var entries = new VersionInfo.Collection();
entries.fetch({
success: function (data) {
that.$el.html(that.template({ items: data.toJSON() }));
}
});
return this;
},
showAddEntry: function() {
if (this.versionInfoEditView != null) {
this.versionInfoEditView.trash();
}
this.versionInfoEditView = new VersionInfoEditView({ el: this.$addEntryArea });
this.$addEntryButton.hide();
this.versionInfoEditView.render();
return false;
}
});
return versionInfoEntriesView;
});
And here's the child view...
define(['ministry', 'models/m-version-info', 'text!templates/version-info/edit-version-info- entry.html', 'jquery.custom'],
function (Ministry, VersionInfo, TemplateSource) {
var editVersionInfoView = Ministry.View.extend({
template: Handlebars.compile(TemplateSource),
initialize: function () {
this.$dbVersionInput = this.$('#dbVersion');
this.$tagInput = this.$('#tag');
},
render: function () {
this.$el.html(this.template());
return this;
},
events: {
'submit .edit-version-info-form': 'saveEntry'
},
saveEntry: function() {
var entry = new VersionInfo.Model({ dbVersion: this.$dbVersionInput.val(), tag: this.$tagInput.val() });
entry.save({
success: function() {
alert('Your item has been saved');
}
});
return false;
}
});
return editVersionInfoView;
});
And the main template...
<h2>Version Info</h2>
<div id="info">
<a id="addVersionInfoEntryButton" href="#/versioninfo">Add manual entry</a>
<div id="addVersionInfoEntryArea">
</div>
<ul id="items">
{{#each items}}
<li>{{dbVersion}} | {{tag}}</li>
{{/each}}
</ul>
</div>
And the edit template...
<form class="edit-version-info-form">
<h3>Create a new entry</h3>
<label for="dbVersion">DB Version</label>
<input type="text" id="dbVersion" maxlength="10" />
<label for="tag">Tag</label>
<input type="text" id="tag" />
<button type="submit" id="newEntryButton">Create</button>
</form>
I'm fairly new to backbone so I may well be doing something totally wrong, but I can't see anything wrong with the approach so far and it's not throwing any errors.
OK - Fix as follows after some facepalming...
define(['ministry', 'jquery', 'models/m-version-info', 'views/about/v-edit-version-info-entry', 'text!templates/version-info/version-info.html'],
function(Ministry, $, VersionInfo, VersionInfoEditView, TemplateSource) {
var versionInfoEntriesView = Ministry.View.extend({
el: '#mainAppArea',
template: Handlebars.compile(TemplateSource),
versionInfoEditView: null,
$addEntryArea: undefined,
$addEntryButton: undefined,
initialize: function () {
},
events: {
'click #addVersionInfoEntryButton': 'showAddEntry'
},
render: function () {
var that = this;
var entries = new VersionInfo.Collection();
entries.fetch({
success: function (data) {
that.$el.html(that.template({ items: data.toJSON() }));
that.$addEntryArea = that.$('#addVersionInfoEntryArea');
that.$addEntryButton = that.$('#addVersionInfoEntryButton');
}
});
return this;
},
showAddEntry: function (e) {
e.preventDefault();
if (this.versionInfoEditView != null) {
this.versionInfoEditView.trash();
}
this.versionInfoEditView = new VersionInfoEditView({ el: this.$addEntryArea });
this.$addEntryButton.hide();
this.$addEntryArea.append('Do I want to put it here?');
this.versionInfoEditView.render();
}
});
return versionInfoEntriesView;
});
The issue was due to the fact that I was setting the internal element variables within the view before the completion of the render, so the elements were linked up to nothing. I resolved this by extracting the element initiation to the end of the render success callback.
Here's the fix again...
define(['ministry', 'jquery', 'models/m-version-info', 'views/about/v-edit-version-info-entry', 'text!templates/version-info/version-info.html'],
function(Ministry, $, VersionInfo, VersionInfoEditView, TemplateSource) {
var versionInfoEntriesView = Ministry.View.extend({
el: '#mainAppArea',
template: Handlebars.compile(TemplateSource),
versionInfoEditView: null,
$addEntryArea: undefined,
$addEntryButton: undefined,
initialize: function () {
},
events: {
'click #addVersionInfoEntryButton': 'showAddEntry'
},
render: function () {
var that = this;
var entries = new VersionInfo.Collection();
entries.fetch({
success: function (data) {
that.$el.html(that.template({ items: data.toJSON() }));
that.$addEntryArea = that.$('#addVersionInfoEntryArea');
that.$addEntryButton = that.$('#addVersionInfoEntryButton');
}
});
return this;
},
showAddEntry: function (e) {
e.preventDefault();
if (this.versionInfoEditView != null) {
this.versionInfoEditView.trash();
}
this.versionInfoEditView = new VersionInfoEditView({ el: this.$addEntryArea });
this.$addEntryButton.hide();
this.$addEntryArea.append('Do I want to put it here?');
this.versionInfoEditView.render();
}
});
return versionInfoEntriesView;
});
The issue was due to the fact that I was setting the internal element variables within the view before the completion of the render, so the elements were linked up to nothing. I resolved this by extracting the element initiation to the end of the render success callback.

Categories

Resources