React.js - Search with pagination interaction - javascript

I am building my first application with React.js.
The application uses external movie API (https://www.themoviedb.org/documentation/api) to search and list movies.
The request takes parameters "query" and optionally "page". The response already contains total number of results and total number of existing pages.
The sample response looks something like this:
{
"page": 1,
"results": [
{
"adult": false,
"backdrop_path": "/8uO0gUM8aNqYLs1OsTBQiXu0fEv.jpg",
"genre_ids": [18],
"id": 550,
"original_language": "en",
"original_title": "Fight Club",
"overview": "A ticking-time-bomb insomniac and a slippery soap salesman channel primal male aggression into a shocking new form of therapy. Their concept catches on, with underground \"fight clubs\" forming in every town, until an eccentric gets in the way and ignites an out-of-control spiral toward oblivion.",
"release_date": "1999-10-14",
"poster_path": "/811DjJTon9gD6hZ8nCjSitaIXFQ.jpg",
"popularity": 4.39844,
"title": "Fight Club",
"video": false,
"vote_average": 7.8,
"vote_count": 3527
},
{
"adult": false,
"backdrop_path": "/qrZssI8koUdRxkYnrOKMRY3m5Fq.jpg",
"genre_ids": [
27
],
"id": 289732,
"original_language": "zh",
"original_title": "Zombie Fight Club",
"overview": "It's the end of the century at a corner of the city in a building riddled with crime - Everyone in the building has turned into zombies. After Jenny's boyfriend is killed in a zombie attack, she faces the challenge of surviving in the face of adversity. In order to stay alive, she struggles with Andy to flee danger.",
"release_date": "2014-10-23",
"poster_path": "/7k9db7pJyTaVbz3G4eshGltivR1.jpg",
"popularity": 1.621746,
"title": "Zombie Fight Club",
"video": false,
"vote_average": 3.5,
"vote_count": 2
}
],
"total_pages": 1,
"total_results": 2
}
The structure, that I have picked is the following:
MovieSearchPage
- SearchForm
- SearchResults
--MovieBox
--Pagination
---Page
Here's my solution, which almost works fine:
var SearchResults = React.createClass({
handlePageChange: function(pageNum) {
this.props.onPageChange(pageNum);
},
render: function() {
var movies = [];
this.props.data.results.forEach(function(movie) {
movies.push(<MovieBox movie={movie} key={movie.id}/>);
});
return (
<div>
<h3>Search results ({this.props.data.total_results} found)</h3>
<Pagination onPageClick={this.handlePageChange} currentPage={this.props.data.page} totalPages={this.props.data.total_pages} totalResults={this.props.data.total_results}/>
<div>{movies}</div>
</div>
);
}
});
var SearchBar = React.createClass({
getInitialState: function() {
return {query: ''};
},
handleQueryChange: function(e) {
this.setState({query: e.target.value});
},
handleSubmit: function(e) {
e.preventDefault();
var query = this.state.query.trim();
if (!query || query.length < 3) {
return;
}
this.props.onSearch(query);
},
render: function() {
return (
<form className="searchForm" onSubmit={this.handleSubmit}>
<input
type="text"
name="query"
placeholder="Search for a movie..."
value={this.state.query}
onChange={this.handleQueryChange}
/>
<input type="submit" value="Search Movie"/>
</form>
);
}
});
var MovieBox = React.createClass({
render: function() {
var m = this.props.movie;
return (
<div className="movieBox">
<div>{m.original_title} ({m.release_date})</div>
</div>
);
}
});
var Pagination = React.createClass({
render: function() {
var pages = [];
var total = this.props.totalPages;
for (var i = 1; i <= total; i++) {
pages.push(<Page key={i} onPageClick={this.props.onPageClick} pageNum={i} active={(this.props.currentPage == i) || false}/>);
}
return (
<div className="pagination">{pages}</div>
);
}
});
var Page = React.createClass({
handleClick: function(e) {
var pageNum = e.target.innerHTML;
this.props.onPageClick(pageNum);
},
render: function() {
return (
<a href="#" onClick={this.handleClick} className={this.props.active ? 'active' : ''}>{this.props.pageNum}</a>
)
}
});
var MovieSearchPage = React.createClass({
getInitialState: function() {
return {query: '', pageNum: 1, data: {results: [], total_pages: 0, total_results: 0}};
},
initSearch: function(query) {
this.setState({query: query});
this.doSearch(query, this.state.pageNum);
},
nextPage: function(pageNum) {
this.setState({pageNum: pageNum});
this.doSearch(this.state.query, pageNum);
},
doSearch: function(query, pageNum) {
$.ajax({
url: this.props.url,
dataType : 'json',
type: 'POST',
data: {query: query, api_key: this.props.apiKey, page: pageNum},
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
render: function() {
return (
<div>
<SearchBar onSearch={this.initSearch}/>
<SearchResults onPageChange={this.nextPage} data={this.state.data}/>
</div>
);
}
});
ReactDOM.render(
<MovieSearchPage url="http://api.themoviedb.org/3/search/movie" apiKey=""/>,
document.getElementById('container')
);
The problem, that I am having here, is that initial search is performed within SearchForm, using "query" state, however, I want to perform search requests to API from clicking on pagination (same query string, but different page number) without reloading the page.
I managed to achieve this by passing relevant parent method from MovieSearchPage all the way down to Page component.
This works fine, but I firmly believe, that this is not the most elegant solution to do this - the same method is passed multiple times plus I had to duplicate "query" state in both MovieSearchBox and SearchForm.
Please forgive me being a noob, but this is really the first time I am using a complex front-end framework. What is the proper way to do it in order to achieve this?
Despite situation being very generic, I was not able to find anything on the web.
Thanks in advance!

Related

How do I map the return of my dataSource to a Kendo AutoComplete UI

I have a Kendo jquery AutoComplete UI component with a remote REST web API dataSource. I want to map the responce of the API to the autoComplete. I have it displaying the choices but when I examine the select function I just see what I've selected. What I want is the complete json object on the select event. Or at least what I really need is the unitKey property.
my code:
//create AutoComplete UI component
$("#autoAddress").kendoAutoComplete({
minLength: 3,
dataTextField: "address",
filter: "startswith",
placeholder: "Address lookup ...",
select : function(e) {
var item = e.item;
console.log(item.text());
},
dataSource: new kendo.data.DataSource ({
serverFiltering: true,
transport: {
read: {
url: "https://testapi.mydomain.com/homeinfo/api/address/",
// dataType: "jsonp",
data: {
q : function() {
return $("#autoAddress").data("kendoAutoComplete").value();
},
maxRows: 30
}
}
},
schema : {
data: function(response){
return response;
}
}
})
});
sample date from api call:
[
{
"unitKey": "S37.75 ",
"address": "1234 ADDISON AVE"
},
{
"unitKey": "S22.215 ",
"address": "1234 AUGUSTINE DR"
},
{
"unitKey": "L100.9 ",
"address": "1234 AVENIDA DE LAS CASAS"
}
]
I'm trying to get the "unitKey" when the user makes a selection from the autocomplete component.
You want to look at e.dataItem rather than e.item:
console.log(e.dataItem.unitKey);
In cases like this I usually log 'e' itself to the console or breakpoint with the debugger to inspect it to see what it contains since the documentation is not always as comprehensive as it could be.

Execute javascript code delivered via PHP using AJAX

I have been stumped on this for about 2 hours now.
My problem is that I need to load an array from a MySQL database using PHP and Ajax, and use the array in JavaScript.
I got that part working fine however the part where it references "onClick" and contains a function to run does not work. It provides numerous errors which say the exact same thing.
Uncaught RangeError: Maximum call stack size exceeded
at buttons.<computed>.onClick (app.js:1281)
The example of the array is the following:
[
{
"text": "Lost to Competitor",
"onClick": "closeOpportunity(\"Lost to Competitor\", el, stages)"
},
{
"text": "No Budget \/ Lost Funding",
"onClick": "closeOpportunity(\"No Budget \/ Lost Funding\", el, stages)"
},
{
"text": "No Decision \/ Non-responsive",
"onClick": "closeOpportunity(\"No Decision \/ Non-responsive\", el, stages)"
},
{
"text": "Price",
"onClick": "closeOpportunity(\"Price\", el, stages)"
},
{
"text": "Other",
"onClick": "closeOpportunity(\"Other\", el, stages)"
},
{
"text": "Won via Another Opportunity",
"onClick": "closeOpportunity(\"Won via Another Opportunity\", el, stages)"
}
]
My code for loading the array is the following:
function closeOpportunity(name, el, stages) {
$$("#opportunity_loss_reason2").text(name);
$$("#closedType").text("Closed Lost");
$$("#convertToProject").hide();
$$("#lostReasonLI").show();
upStepper(el, stages);
}
var stages = [
"enquiry",
"qualification",
"proposal",
"negotiation",
"closed"
];
var buttons = [];
app.request.json('scripts/lostButtonsArray.php', function (data) {
buttons = data;
console.log(buttons);
});
buttons.forEach((v, i) => {
console.log(v['onClick']);
buttons[i]['onClick'] = function() { window.eval.call(window, v['onClick'])(el, stages); };
});
app.dialog.create({
title: 'ECOM',
text: 'Why was the opportunity lost?',
cssClass: 'custom-dialog',
closeByBackdropClick: 'true',
buttons: buttons,
verticalButtons: true,
}).open();
I have already tried using regular eval() and loading the code directly without any sort of helper (eval, window.eval).
I will be happy to provide more information to help solve this problem if I haven't provided enough information.
Found the solution.
I was trying to load "name" instead of "text"
Working function
app.request.json('scripts/lostButtonsArray.php', function (data) {
buttons = data;
buttons.forEach((v, i) => {
buttons[i]['onClick'] = function() { eval('closeOpportunity')(v['text'], el, stages); };
});
});

Using fetch to post JSON data

I'm trying to do a post of multiple points of data from a form input.
However, the form data doesn't reach json output payload (I checked the network output). It seems to never get triggered.
If there's a better way to re-write this- I am open to it
I've put it all into Codepen- http://codepen.io/yarnball/pen/LRVgpo?editors=1011
The data needs to be posted in this exact way:
{
"title": "SAMPLE",
"tag": [
{
"name": "Movie",
"taglevel": 1,
}
],
"info": []
}
Post method
var Postapi = React.createClass({
componentWillMount () {
var form = document.querySelector('form')
return fetch('http://localhost:8000/api/Data/', {
method: 'POST',
body: JSON.stringify({
title: this.state.itemtitle,
tag:[
{name:this.state.tagtitle,
taglevel:this.state.taglevel}
],
info:[]
})
})
},
Sample return
<form onSubmit={this.handleSubmit}>
...
<input
placeholder="Item Title"
type="text"
itemtitle={this.state.itemtitle}
onChange={this.handleChange}
/>
Initial state & submit
getInitialState: function() {
return {
itemtitle: [],
tagtitle: [],
taglevel: [],
tagoptions: Exampledata
};
},
handleChange: function(event) {
this.setState({itemtitle: event.target.itemtitle});
this.setState({tagtitle: event.target.tagtitle});
this.setState({tagname: event.target.tagname});
},
handleSubmit: function(e) {
e.preventDefault();
var itemtitle = this.state.itemtitle
var tagtitle = this.state.tagtitle
var taglevel = this.state.taglevel
this.setState({itemtitle: '', text: ''});
},
you must call your fetch function in handlesubmit function...according to this link , componentWillMount : is executed before rendering, on both server and client side. so your form is empty.
you need read more about react life cycle.

Use data source from a function (dynamic datasource) - Select2 v4

In the previous versions one can easily get this done by:
someDynamicData=function(){
//return some data...
}
$("#lga").select2({
width: '100%',
allowClear: false, placeholder: "--- please select ---",
data: function () {
return {results: someDynamicData, text: 'name'};
}
});
I this dosen't work with the new select2 v4. How do I achieve this in the new select2 v4.
For anyone that might be stocked as much as I am with select2 v4. I got a workaroud. Please note that I am some javascript guru and there might be some better way around this issue. For the time being this is rocking:
Case study: I have a chained select options which share from a list JSON Objects: here I am show only Subject and Level example just to keep it simple. When a Subject is selected I what to initialize the Level data source from the selected Subject's data source
function getDynamicData(){
return [
{"code": "ACC", "name": "ACCOUNTING", "levels": [
{"id": 3, "name": "SS 1", "description": "Senior Secondary School 1", "type": "Standard", "resultTemplates": []},
....
]}
....
];
}
$.fn.select2.amd.require(['select2/data/array', 'select2/utils'],
function (ArrayData, Utils) {
function CustomData($element, options) {
CustomData.__super__.constructor.call(this, $element, options);
}
Utils.Extend(CustomData, ArrayData);
CustomData.prototype.query = function (params, callback) {
subjects = getDynamicData();
$.map(subjects, function (d) {//Required only when the object do not have either id/text
d.id = d.code;
d.text = d.name;
return d;
});
callback({results: subjects});
};
$("#subjects").select2({
width: '100%',
dataAdapter: CustomData
});
}
);
This works fine but then I realized that I am repeating many things hence it is time for cleanup
//Added this function to a javascript file on my template (shared across pages)
function initMySelect2(myQuery, mySelector, props) {
$.fn.select2.amd.require(['select2/data/array', 'select2/utils'],
function (ArrayData, Utils) {
function CustomData($element, options) {
CustomData.__super__.constructor.call(this, $element, options);
}
Utils.Extend(CustomData, ArrayData);
CustomData.prototype.query = myQuery;//Pass the query function as a parameter
$(mySelector).select2(props(CustomData));//Initialise the select2 with the custom data
}
);
}
//Used this to initialize select2
initMySelect2(function (params, callback) {
callback({results: getDynamicData()});
},
"#subjects",
function (myCustomData) {
return {
width: '100%',
placeholder: '---please select subjects ---',
dataAdapter: myCustomData
};
}
);
Reference https://select2.github.io/announcements-4.0.html
I hope this will help someone out there.

FuelUX datagrid not loading (using example)

I'm new to FuelUX so I was trying to get this to work, based on the example provided:
require(['jquery','data.js', 'datasource.js', 'fuelux/all'], function ($, sampleData, StaticDataSource) {
var dataSource = new StaticDataSource({
columns: [{property:"memberid",label:"LidId",sortable:true},{property:"name",label:"Naam",sortable:true},{property:"age",label:"Leeftijd",sortable:true}],
data: sampleData.memberdata,
delay: 250
});
$('#MyGrid').datagrid({
dataSource: dataSource,
stretchHeight: true
});
});
});
With this as the data:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(factory);
} else {
root.sampleData = factory();
}
}(this, function () {
return {
"memberdata": [{
"memberid": 103,
"name": "Laurens Natzijl",
"age": "25"
}, {
"memberid": 104,
"name": "Sandra Snoek",
"age": "25"
}, {
"memberid": 105,
"name": "Jacob Kort",
"age": "25"
}, {
"memberid": 106,
"name": "Erik Blokker",
"age": "25"
}, {
"memberid": 107,
"name": "Jacco Ruigewaard",
"age":"25"
},{ /* etc */ }]
}
}));
I've got no console errors, no missing includes. Everthing works just fine - it even looks like it's loading. Except nothing shows up in the datagrid but '0 items'.
Any suggestions? I think I did everything the example provided...
EDIT: 14:33 (Amsterdam)
There seems to be a difference when I put this in console:
My page:
require(['jquery','data.js','datasource.js', 'fuelux/all'], function ($, sampleData, StaticDataSource) {
var dataSource = new StaticDataSource({
columns: [{property:"memberid",label:"LidId",sortable:true},{property:"name",label:"Naam",sortable:true},{property:"age",label:"Leeftijd",sortable:true}],
data: sampleData.memberdata,
delay: 250
});
console.debug(dataSource);
});
1st row in console:
function localRequire(deps, callback, errback) { /* etc */ }
2nd row in console:
StaticDataSource {_formatter: undefined, _columns: Array[3], _delay: 250, _data: Array[25], columns: function…}
FuelUX Example:
require(['jquery', 'sample/data', 'sample/datasource', 'sample/datasourceTree', 'fuelux/all'], function ($, sampleData, StaticDataSource, DataSourceTree) {
var dataSource = new StaticDataSource({
columns: [{property: 'toponymName',label: 'Name',sortable: true}, {property: 'countrycode',label: 'Country',sortable: true}, {property: 'population',label: 'Population',sortable: true}, {property: 'fcodeName',label: 'Type',sortable: true}],
data: sampleData.geonames,
delay: 250
});
console.debug(dataSource);
});
1st row in console:
StaticDataSource {_formatter: undefined, _columns: Array[4], _delay: 250, _data: Array[146], columns: function…}
2nd row in console:
function (deps, callback, errback, relMap) { /* etc */ }
Maybe this will help you help me :)
I didn't see all of the information I needed to provide a finite answer. The real magic is the datasource.js file (which you had not provided).
I thought an easier way of demonstrating all the necessary pieces would be to put together a JSFiddle showing your data in use and all the pieces that were necessary.
Link to JSFiddle of Fuel UX Datagrid sample with your data
Adam Alexander, the author of the tool, also has written a valuable example of using the dataGrid DailyJS Fuel UX DataGrid
// DataSource Constructor
var StaticDataSource = function( options ) {
this._columns = options.columns;
this._formatter = options.formatter;
this._data = options.data;
this._delay = options.delay;
};
StaticDataSource.prototype = {
columns: function() {
return this._columns
},
data: function( options, callback ) {
var self = this;
var data = $.extend(true, [], self._data);
// SEARCHING
if (options.search) {
data = _.filter(data, function (item) {
for (var prop in item) {
if (!item.hasOwnProperty(prop)) continue;
if (~item[prop].toString().toLowerCase().indexOf(options.search.toLowerCase())) return true;
}
return false;
});
}
var count = data.length;
// SORTING
if (options.sortProperty) {
data = _.sortBy(data, options.sortProperty);
if (options.sortDirection === 'desc') data.reverse();
}
// PAGING
var startIndex = options.pageIndex * options.pageSize;
var endIndex = startIndex + options.pageSize;
var end = (endIndex > count) ? count : endIndex;
var pages = Math.ceil(count / options.pageSize);
var page = options.pageIndex + 1;
var start = startIndex + 1;
data = data.slice(startIndex, endIndex);
if (self._formatter) self._formatter(data);
callback({ data: data, start: 0, end: 0, count: 0, pages: 0, page: 0 });
}
};
If you were to provide your markup and what your "datasource.js" file contains, I may be able to help you further.
I think the demonstration provides much information on any pieces you may not have understood.
Adding on to creatovisguru's answer:
In his JSFiddle example, pagination is broken. To fix it, change the following line:
callback({ data: data, start: start, end: end, count: count, pages: pages, page: page });
I had the exact same issue, when tried to integrate with Django. The issue I believe is on this line :
require(['jquery','data.js','datasource.js', 'fuelux/all'], function ($, sampleData, StaticDataSource) {
I was not able to specify file extension, my IDE (pycharm), would mark "red", when used "data.js", so it needs to stay without an extension, such as "sample/data"
What I end up doing to make it work, is downloading the full fuelux directory from github in /var/www/html on a plain Apache setup ( no django, to avoid URL.py issues for static files ) and everything works using their example. Here are the steps to get you started :
cd /var/www/html
git clone https://github.com/ExactTarget/fuelux.git
and you will end up with fuelux in /var/www/html/fuelux/
in your browser, navigate to : http://foo.com/fuelux/index.html ( assuming your default document root is /var/www/html )
good luck!

Categories