Dynamically generate React component names - coffeescript

I'm having trouble finding a solution to this. How can I convert the code below into something more dynamic and succinct?
OneComponent = require ('./OneComponent')
TwoComponent = require ('./TwoComponent')
ThreeComponent = require ('./ThreeComponent')
Example = React.createClass
render: ->
filter = #props.filterState
if filter is 'something'
tableCmpt =
<div>
<OneComponent
tasks={#props.tasks}
/>
</div>
if filter is 'somethingElse'
tableCmpt =
<div>
<TwoComponent
tasks={#props.tasks}
/>
</div>
##... etc
return tableCmpt

I've done something like this.
var Components = {
'something': require('./Component'),
'somethingElese': require('./Component2')
};
Example = React.createClass({
render: function() {
var component = Components[this.props.filter];
return <div><component tasks={this.props.tasks}/></div>;
}
});

I couldn't get Crob's answer to work (though I appreciate the hash table idea), but it led me to find a solution - I just skipped the jsx step and used the compiled js:
React.createElement(component, {tasks: this.props.tasks} )
So all in all:
var Components = {
'something': require('./Component'),
'somethingElese': require('./Component2')
};
Example = React.createClass({
render: function() {
var component = Components[this.props.filter];
React.createElement(component, {tasks: this.props.tasks} )
}
});

Related

Populating a select form by iterating through an object

Attempting to populate a select form using an object I'm passing to my component as a prop. The object looks like this: {24: {14: 64.99, 20: 89.99, 26: 114.99}, 30: {14: 74.99, 20: 99.99, 26: 124.99} and I'm attempting to isolate the 24 and the 30 as values in my form. Here's the relevant code:
import React, { Component } from 'react';
import { Row, Input } from 'react-materialize';
class HeightPicker extends Component {
constructor(props){
super(props);
this.state = {
height: '',
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({height: e.target.value});
}
displayHeights(){
let prices = this.props.prices;
let height;
let heights = [];
console.log(prices);
for (height in prices) {
heights.push(height)
};
console.log(heights);
heights.forEach(function(h) {
return(<option value={h}>{h}</option>);
});
}
render() {
return(
<div>
<Row>
<Input l={12} value={this.state.height} onChange={this.handleChange} type='select' label="Height">
{this.displayHeights()}
</Input>
</Row>
</div>
)
};
};
export default HeightPicker;
As constructed above, it's returning a blank form. If I hardcode options into my render function it works, therefore I'm assuming my issue is arising through my displayHeights function. But I also was running into some issues earlier with React Materialize and the version of React I was running -- had to downgrade versions from 16.0.0 to 15.6.2 -- so I'm wondering if it's related to that. Any help would be appreciated. Thanks.
Use map instead of forEach in displayHeights method
forEach method does some operation on each element of array or collection, but does not return the modified element, map method returns the modified element after some operation
Your implemenetation has two issues
you are using forEach which does not return the modified elements
you did not return the array containing options, in your case , heights
The modified code block will be
return heights.map(function(h) {
return(<option value={h}>{h}</option>);
});
This should be your displayHeights function. Here is a WORKING DEMO
You need to return the array back in your function and also map is what you should be using.
displayHeights(){
let prices = this.props.prices;
let height;
let heights = [];
console.log(prices);
for (height in prices) {
heights.push(height)
};
console.log(heights);
return heights.map(function(h, i) {
return(<option key={i} value={h}>{h}</option>);
});
}
Using forEach
let heightsJSX = [];
heights.forEach(function(h, i) {
heightsJSX.push(<option key={i} {value={h}>{h}</option>));
});
return heights;
For efficiency's sake, why not just map over the keys. Here's a one-liner.
displayHeight() {
return Object.keys(this.props.prices).map(key => <option key={key} value={key}>{key}</option>);
}

React onClick event is not fired when element is created in for loop

I was trying to solve this strange problem all day, but didn't managed to. This is one of the first days I am trying out React, so maybe I am missing something.
ParentComponent.cjsx
module.exports = React.createClass
getInitialState: ->
{
items: []
}
componentDidMount: ->
request.get(constants.API_ROOT + #props.source)
.end((err, res) =>
#setState({items: res.body})
)
render: ->
`
// First try: DOES NOT WORK
var items = [];
for(var i = 0; i < this.state.items.length; i++) {
var item = this.state.items[i];
items.push(<ChildItem key={item.id} id={item.id} name={item.name} src={item.image_url_640x480} />)
}
console.log(['items1', items]);
// Second try: DOES NOT WORK
var origThis = this;
var items2 = this.state.items.map(function (item) {
return (<ChildItem key={item.id} id={item.id} name={item.name} src={item.image_url_640x480} />);
}.bind(origThis), origThis);
console.log(['items2', items2]);
`
// Creating elements by hand (WORKS, but not helpful at all)
items3 = [
<ChildItem key=23 id=31 name='ddd' src='adasdas' />,
<ChildItem key=12 id=13 name='bg' src='/media/cache/de/ba/deba6d1545e209b0416b501c61fe031f.jpg' />
]
console.log(items3)
<div id="image-layer-selector" className="pure-g">{items1} {items2} {items3}</div>
ChildItem.cjsx
module.exports = React.createClass
getInitialState: ->
selected: false
handleClick: ->
console.log 'clicked'
#setState selected: true
render: ->
elemClasses = classnames('pure-u-1-2', 'selector-element', {'selected': #state.selected})
<div className={elemClasses} onClick={#handleClick}>
{#props.name} - {#props.id}
<img className="pure-img" src={constants.API_ROOT + #props.src}/>
</div>
ChildItem onClick handler is fired only when elements are set by hand. What am I missing? I tried a lot of possible ways in .cjsx, plain .jsx, .map function, plain JS for loop etc. None of these seemed to work. Console doesn't contain any errors.
Using react 13.3.
EDIT. Seems like onClick handler doesn't work only when items are set in componentDidMount using setState. Identical problem without solution is here: React - Attaching click handler to dynamic children
Finally found the problem. I haven't done any deeper investigation why this didn't work, but the problem was that in my main file I imported React as require('React'), but on other components as require('React/addons'). After importing React everywhere from react/addons everything works as expected.

How to include var in partials calls

Let's say I have a website where for each main section I have a specific sidebar.
Currently I have a single sidebar file, where im using categories to filter the correct content to show like this:
{{#inArray page.categories "Components"}}
<section class="sg-index">
<ul>
<li {{#is title "DetailContent"}} class="active"{{/is}}>
DetailContent
</li>
However my goal is to have these sidebar files located at each section folder, along with the section files.
How can I include the {{dirname}} variable in the partials call {{> sidebar}}?
This should be possible with a conditional block helper, like {{with}} and the include helper,
B you could also create a custom helper, something like this:
var path = require('path');
var _ = require('lodash');
var matter = require('gray-matter');
module.exports.register = function (Handlebars, options, params) {
var assemble = params.assemble;
var grunt = params.grunt;
var opts = options || {};
Handlebars.registerHelper('sidenav', function(page, context) {
if(page.published !== false && page.sidenav) {
if(!Array.isArray(assemble.partials)) {
assemble.partials = [assemble.partials];
}
var filepath = _.first(_.filter(assemble.partials, function(fp) {
return path.basename(fp, path.extname(fp)) === page.sidenav;
}));
// Process context, using YAML front-matter,
// grunt config and Assemble options.data
var pageObj = matter(filepath) || {};
var metadata = pageObj.context || {};
context = _.extend(this, opts.data[page.sidenav], metadata, context);
var partial = Handlebars.partials[page.sidenav];
var template = Handlebars.compile(partial);
var output = template(context);
// Prepend output with the filepath to the original partial
// for debugging
var sidenav = opts.sidenav || opts.data.sidenav || {};
if(sidenav.origin === true) {
output = '<!-- ' + filepath + ' -->\n' + output;
}
return new Handlebars.SafeString(output);
}
});
};
then use it in your markup like this:
<div class="sidebar" role="complementary">
<ul class="nav sidenav">
{{sidenav this}}
</ul>
</div>

View doesn't update on observablearray.remove(item) in knockout without call to applyBindings

I am learning to use MVC 4/MVVM/Knockout for a web-managed data project. I have been running into a problem updating the View when using the remove function on an observable array. The updates happen when using push or unshift, but not remove. Using the debugger in chrome I can see that the data is being removed from the array, the update event just isn't working.
Snippet from the html is the table below, there is a form I did not include for adding or editing data.
<div id="MessageDiv" data-bind="message: Message"></div>
<div class="tableContainer hiddenHead">
<div class="headerBackground"></div>
<div class="tableContainerInner">
<table id="adapter-table" class="grid" data-bind="sortTable: true">
<thead>
<tr>
<th class="first">
<span class="th-inner">Name</span>
</th>
<th>
<span class="th-inner">DeviceID</span>
</th>
<th>
<span class="th-inner"></span>
</th>
<th>
<span class="th-inner"></span>
</th>
</tr>
</thead>
<tbody data-bind="template: { name: 'AdaptersTemplate', foreach: Adapters }">
</tbody>
</table>
<script id="AdaptersTemplate" type="text/html">
<tr>
<td data-bind="text: Name"></td>
<td data-bind="text: DeviceID"></td>
<td>Edit
<td>Delete
</tr>
</script>
</div>
<input type="button" data-bind='click: addAdapter' value="Add New Adapter" />
<input type="button" data-bind='click: saveAll' value="Save Changes" id="SaveChangesButton" />
</div>
My javascript has been set up to manage the VM as restful and caches the changes. Add, Edit, and Saving/Deleting data all seems to work without throwing errors that I am seeing in the debugger in Chrome. Confirming changes seems to work fine and makes the changes to the database as expected.
$(function () {
var viewModel = new AdaptersModel();
getData(viewModel);
});
function getData(viewModel) {
$.getJSON("/api/AdapterList",
function (data) {
if (data && data.length > 0) {
viewModel.SetAdaptersFromJSON(data);
}
ko.applyBindings(viewModel);
});
}
//#region AdapterVM
function Adapter(name, siFamily, deviceIDs) {
var self = this;
self.Name = ko.observable(name);
self.DeviceID = ko.observable(deviceIDs);
self.ID = 0;
}
function AdaptersModel() {
var self = this;
self.Adapters = ko.observableArray([]);
self.DeleteAdapters = ko.observableArray([]);
self.NewAdapter = ko.observable(new Adapter("", "", "", ""));
self.Message = ko.observable("");
self.SetAdaptersFromJSON = function (jsData) {
self.Adapters = ko.mapping.fromJS(jsData);
};
//#region Edit List Options: confirmChanges
self.confirmChanges = function () {
if (self.NewAdapter().ID == 0) {
self.Adapters.push(self.NewAdapter());
}
};
//#endregion
//#region Adapter List Options: addAdapter, selectItem, deleteItem, saveAll
self.addAdapter = function () {
self.NewAdapter(new Adapter("", "", "", ""));
};
self.selectItem = function (item) {
self.NewAdapter(item);
};
self.deleteItem = function(item) {
self.DeleteAdapters.push(item.ID());
self.Adapters.remove(item);
};
self.saveAll = function () {
if (self.Adapters && self.Adapters().length > 0) {
var filtered = ko.utils.arrayFilter(self.Adapters(),
function(adapter) {
return ((!isEmpty(adapter.Manufacturer())) &&
(!isEmpty(adapter.Name())) &&
(!isEmpty(adapter.DeviceIDs()))
);
}
);
var updateSuccess = true;
if (self.DeleteAdapters().length > 0) {
jsonData = ko.toJSON(self.DeleteAdapters());
$.ajax({
url: "/api/AdapterList",
cache: false,
type: "DELETE",
data: jsonData,
dataType: "json",
contentType: "application/json; charset=utf-8",
success: function () { updateSuccess = true; },
error: function () { updateSuccess = false; }
});
}
var jsonData = ko.toJSON(filtered);
$.ajax({
url: "/api/AdapterList",
type: "POST",
data: jsonData,
dataType: "json",
contentType: "application/json; charset=utf-8",
success: function(data) {
self.SetAdaptersFromJSON(data);
updateSuccess = true && updateSuccess;
},
error: function () { updateSuccess = false; }
});
if (updateSuccess == true) { self.Message("Update Successfull"); }
else { self.Message("Update Failed"); }
}
};
//#endregion
}
//#endregion
ko.bindingHandlers.message = {
update: function(element, valueAccessor) {
$(element).hide();
ko.bindingHandlers.text.update(element, valueAccessor);
$(element).fadeIn();
$(element).fadeOut(4000);
}
};
ko.bindingHandlers.sortTable = {
init: function (element, valueAccessor) {
setTimeout(function () {
$(element).addClass('tablesorter');
$(element).tablesorter({ widgets: ['zebra'] });
}, 0);
}
};
function isEmpty(obj) {
if (typeof obj == 'undefined' || obj === null || obj === '') return true;
if (typeof obj == 'number' && isNaN(obj)) return true;
if (obj instanceof Date && isNaN(Number(obj))) return true;
return false;
}
The specific script portion that is failing to update my html table is:
self.deleteItem = function(item) {
self.DeleteAdapters.push(item.ID());
self.Adapters.remove(item);
};
Everything seems to work except for the remove, so I seem to be at a loss for what to look at next, and I am too new to javascript or knockout to know if this is a clue: If I run ko.applyBindings() command in the self.deleteItem function, I get the update to happen but it does give me an unhandled error:
Uncaught Error: Unable to parse bindings.
Message: ReferenceError: Message is not defined;
Bindings value: message: Message
Message was defined in the VM before binding... was there something I missed in all this?
In the beginning of your Js file you are defining var viewModel = new AdaptersModel(); but lower you are stating that function Adapter() is the view model in your region declaration. It is making your code difficult to read. I am going to take another stab at what you can do to troubleshoot, but I would suggest that your viewmodel contains the adapters and your model contains a class-like instance of what each adapter should be.
The specific error you are getting is because you are binding Message() to something and then deleting Message(). One thing you could do to trouble shoot this is to change your div to something like :
<div id="MessageDiv" data-bind="with: Message">
<h5 data-bind="message: $data"><h5>
</div>
If you could create a fiddle I could give a more definite example of why, but basically if Message() is blank the with binding should not show the header which is undefined after deletion.
What you probably need to do though is look at what is being sent as 'item' and make sure it is not your viewmodel.
self.deleteItem = function(item) {
console.log(item); // << Check console and see what is being returned
self.DeleteAdapters.push(item.ID());
self.Adapters.remove(item);
};
You are probably deleting more than just a single adapter.
This will lead you the right direction, but I would seriously consider either renaming your code.
There was a lot of help solving surrounding issues but nothing actually solved the "why" of the problem. The updates worked perfectly sometimes but not other times. When I was troubleshooting it and started to get it dumbed down and working in JSFiddle I didn't include the data-bind="sortTable: true" in all my working versions. Apparently, if you sort a table or using the code as I did it will not work. The example code I have seen floating around is here at http://jsfiddle.net/gregmason/UChLF/16/, pertinent code:
ko.bindingHandlers.tableSorter = {
init: function (element) {
setTimeout(function () { $(element).tablesorter(); }, 0);
},
update: function (element, valueAccessor) {
ko.utils.unwrapObservable(valueAccessor()); //just to get a dependency
$(element).trigger("update");
}
};
The errant behavior can be obvious by clicking the delete link on the row.
If you click on the row without sorting, you will see the row disappear correctly.
If you first click on a column to re-sort in a different order, THEN delete the row, it remains in the table and appears to have cached.
This can be handled by binding each of the table headers instead of the table itself and replacing the tableSorter code with a custom sort behavior as discussed in this thread:
knockout js - Table sorting using column headers. The sort replacement is here:
ko.bindingHandlers.sort = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var asc = false;
element.style.cursor = 'pointer';
element.onclick = function(){
var value = valueAccessor();
var prop = value.prop;
var data = value.arr;
asc = !asc;
if(asc){
data.sort(function(left, right){
return left[prop]() == right[prop]() ? 0 : left[prop]() < right[prop]() ? -1 : 1;
});
} else {
data.sort(function(left, right){
return left[prop]() == right[prop]() ? 0 : left[prop]() > right[prop]() ? -1 : 1;
});
}
}
}
};
This has fixed my sorting/editing/deleting issues and a working jsFiddle is here: http://jsfiddle.net/gregmason/UChLF/18/

kendo bind HTML elements to grid selected row/dataItem

I have the following situation (using KendoUI):
I have a grid binded to a datasource.
When I select a row in the grid I invoke its "change" event to get the selected dataItem e show its values through other HTML elements.
Something like the following:
$("grid-element").kendoGrid({
change: setElements
});
function setElements() {
var grid = $("#grid-element").data("kendoGrid");
var selectedItem = grid.dataItem(grid.select());
$("#span-field1").text(selectedItem.field1);
$("#span-field2").text(selectedItem.field2);
$("#span-field3").text(selectedItem.field3);
}
My question is: is it possibile to achieve the same through MVVM or a better KendoUI model binding solution?
So far I have found the following solution:
=== JAVASCRIPT ===
var vm = kendo.observable({
gridSelectedItem: null,
_field1: function() {
return this.get("gridSelectedItem.field1");
},
_field2: function() {
return this.get("gridSelectedItem.field2");
}
});
$("#grid-element").kendoGrid({
change: function(e) {
var selectedItem = this.dataItem(this.select());
vm.set("gridSelectedItem", selectedItem);
}
});
=== HTML ===
<span data-bind="text: _field1"></span>
<span data-bind="text: _field2"></span>
Is there a better way?
Indeed there you are on the right track,
Here is what I can suggest you to try:
=== JAVASCRIPT ===
var vm = kendo.observable({
gridSelectedItem: null
});
$("#grid-element").kendoGrid({
change: function(e) {
var selectedItem = this.dataItem(this.select());
vm.set("gridSelectedItem", selectedItem);
}
});
=== HTML ===
<span data-bind="text: gridSelectedItem.field1"></span>
<span data-bind="text: gridSelectedItem.field2"></span>
It should be slightly more compact.