I not sure why the return is not being passed to the component template. It's listening and the value is found but its not working.
return result.resources; is not returning the data when result.resoures has an array of objects.
here is the autocomplete template auto-complete.hbs
<ul>
<li class='row input-group-lg'>
{{input type="text" value=searchText placeholder="Enter Street Address" class='col-md-6 col-md-offset-3 col-xs-12 street-address'}}
</li>
</ul>
<ul>
{{#each searchResults}}
<li>{{this.name}}</li>
{{/each}}
</ul>
here is auto-complete.js
import Ember from 'ember';
export default Ember.Component.extend({
searchText: null,
searchResults: function() {
var searchText = this.get('searchText');
if(!searchText) {
return;
}
Ember.$.ajax({
url: "http://dev.virtualearth.net/REST/v1/Locations",
dataType: "jsonp",
data: {
key: "",
q: searchText
},
jsonp: "jsonp"
}).then(function(data) {
var result = data.resourceSets[0];
if (result) {
if (result.estimatedTotal > 0) {
return result.resources;
}
}
});
}.property('searchText')
});
The code block
.then(function(data) {
var result = data.resourceSets[0];
if (result) {
if (result.estimatedTotal > 0) {
return result.resources;
}
}
will return from the promise and will not return the value for the computed property, which obviously means that you are not returning anything for the CP.
A possible work around can be
searchResults: function() {
var searchText = this.get('searchText');
var searchResults = Ember.ArrayProxy.create();
if(!searchText) {
return;
}
Ember.$.ajax({
url: "http://dev.virtualearth.net/REST/v1/Locations",
dataType: "jsonp",
data: {
key: "",
q: searchText
},
jsonp: "jsonp"
}).then(function(data) {
var result = data.resourceSets[0];
if (result) {
if (result.estimatedTotal > 0) {
searchResults.set('content',result.resources);
}
}
});
return searchResults;
}.property('searchText')
You can create an arrayproxy and return the arrayproxy for the CP. Upon completion of the promise, set the result as content to the arrayproxy, which will update the template.
Related
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.
After upgrading to 1.13 I get this exception and I can't figure out what's the issue. I also couldn't find any helpful resource which tackles my issue.
It happens for properties I set in another computed property. But this property is definitely called only once.
I have created a jsbin example: http://emberjs.jsbin.com/roderameya/edit?html,js,console,output
UPDATE
As requested I post some code which is more close to my real implementation.
Ember.Controller.extend({
filter: '',
resultCount: {
total: 0,
matches: 0,
mismatches: 0
},
results: function() {
var items = this.get('model'),
matches = [],
resultCount = {};
// Apply search filter
matches = items.filter(function(item){
// Just a dummy filter function
return true;
});
// We need the total number matched by the filter string
resultCount.total = matches.length;
// The already matched items must be narrowed further down
matches = matches.filter(function(item) {
// Another filter function
return true;
});
resultCount.matches = matches.length;
resultCount.mismatches = resultCount.total - matches.length;
this.set('resultCount', resultCount);
return matches;
}.property('model', 'filter'),
});
Try to have your properties not set other properties, but rather depend on each other:
App.IndexController = Ember.Controller.extend({
count: function() {
return this.get("data.length") || 0;
}.property('data.length'),
data: [1,2,3]
});
Updated jsbin for you.
UPDATE
Basically, your resultCount is a temporary variable that we can get rid of, and the rest are just chaining computed properties together:
updated jsbin for advanced example
code:
// Index controller
App.IndexController = Ember.Controller.extend({
count: Ember.computed('filteredItems.length', function(){
return this.get('filteredItems.length');
}),
data: [
Ember.Object.create({ name: "jim", age: 15 }),
Ember.Object.create({ name: "jeff", age: 42 }),
Ember.Object.create({ name: "eric", age: 7 })
],
filter: RegExp(".*"),
ageFilter: -1,
mismatchCount: Ember.computed('filteredItems.length', 'secondPassFilteredItems.length', function() {
return this.get('filteredItems.length') - this.get('secondPassFilteredItems.length');
}),
filteredItems: Ember.computed('data', 'filter', function() {
var controller = this;
return this.get('data').filter(function(item) {
return item.get('name').match(controller.get("filter"));
});
}),
secondPassFilteredItems: Ember.computed('filteredItems', 'ageFilter', function() {
var controller = this;
var ageFilter = controller.get("ageFilter");
if (Ember.isEqual(ageFilter, -1)) {
return this.get('filteredItems');
} else {
return this.get('filteredItems').filter(function(item) {
// more filtering
return item.get("age") <= ageFilter;
});
}
}),
results: Ember.computed.alias('secondPassFilteredItems'),
actions: {
filterByJ: function() {
this.set('filter', new RegExp("j"));
},
filterByEric: function() {
this.set('filter', new RegExp("eric"));
},
filterAllNames: function() {
this.set('filter', new RegExp(".*"));
},
filterYoungins: function() {
this.set('ageFilter', 15);
},
filterAllAges: function() {
this.set('ageFilter', -1);
}
}
});
Template usage:
<script type="text/x-handlebars" data-template-name="index">
<p>Results found: {{count}}</p>
<p>Diff between first and second filter: {{mismatchCount}}</p>
<p>First Filter:
<button {{action 'filterAllNames'}}>all people</button>
<button {{action 'filterByJ'}}>People with J in name</button>
<button {{action 'filterByEric'}}>People named 'eric'</button>
</p>
<p> Second Filter:
<button {{action 'filterAllAges'}}>all ages</button>
<button {{action 'filterYoungins'}}>15 years old or younger</button>
</p>
<ul>
{{#each results as |item|}}
<li>{{item.name}} is {{item.age}} years old</li>
{{/each}}
</ul>
</script>
I am using ReactJs and Mocha and trying few unit tests. I have two mandatory form fields First Name and Last Name. I am trying to test the validation where if only one field is entered, the other field displays the missing value validation error.
Below code simulates the changes to the value and simulates the form submit.
TestUtils.Simulate.change(firstNameElement , {target: {value: 'Joe'}});
TestUtils.Simulate.submit(formElement)
The changed value is reflected in the event handlers on First Name. But, not in the test. So, both the fields display missing value validation failing my test.
What I could be doing wrong here?
Below are code:
//NameForm.jsx
'use strict';
var React=require('react')
var forms = require('newforms')
var NameForm = forms.Form.extend({
firstName: forms.CharField({maxLength: 100, label: "First name(s)"}),
lastName: forms.CharField({maxLength: 100, label: "Last name"}),
cleanFirstName(callback) {
callback(null)
},
render() {
return this.boundFields().map(bf => {
return <div className={'form-group ' + bf.status()}>
<label className="form-label" htmlFor={bf.name}>
<span className="form-label-bold">{bf.label}</span>
</label>
{bf.errors().messages().map(message => <span className="error-message">{message}</span>)}
<input className="form-control" id={bf.name} type="text" name={bf.name} onChange = {this.onChangeHandler}/>
</div>
})
}
, onChangeHandler: function(e){
console.log("onchnage on input is called ----- >> " + e.target.value)
}
})
module.exports = {
NameForm
}
Here is NamePage.jsx:
'use strict';
var React = require('react')
var {ErrorObject} = require('newforms')
var superagent = require('superagent-ls')
var {API_URL} = require('../../constants')
var {NameForm} = require('./NameForm')
var NamePage = React.createClass({
contextTypes: {
router: React.PropTypes.func.isRequired
},
propTypes: {
data: React.PropTypes.object,
errors: React.PropTypes.object
},
statics: {
title: 'Name',
willTransitionTo(transition, params, query, cb, req) {
if (req.method != 'POST') { return cb() }
superagent.post(`${API_URL}/form/NameForm`).send(req.body).withCredentials().end((err, res) => {
if (err || res.serverError) {
return cb(err || new Error(`Server error: ${res.body}`))
}
if (res.clientError) {
transition.redirect('name', {}, {}, {
data: req.body,
errors: res.body
})
}
else {
transition.redirect('summary')
}
cb()
})
}
},
getInitialState() {
return {
client: false,
form: new NameForm({
onChange: this.forceUpdate.bind(this),
data: this.props.data,
errors: this._getErrorObject()
})
}
},
componentDidMount() {
this.setState({client: true})
},
componentWillReceiveProps(nextProps) {
if (nextProps.errors) {
var errorObject = this._getErrorObject(nextProps.errors)
this.refs.nameForm.getForm().setErrors(errorObject)
}
},
_getErrorObject(errors) {
if (!errors) { errors = this.props.errors }
return errors ? ErrorObject.fromJSON(errors) : null
},
_onSubmit(e) {
e.preventDefault()
var form = this.state.form
form.validate(this.refs.form, (err, isValid) => {
if (isValid) {
this.context.router.transitionTo('name', {}, {}, {
method: 'POST',
body: form.data
})
}
})
},
render() {
return <div>
<h1 className="heading-large">Your name</h1>
<form action='name' method="POST" onSubmit={this._onSubmit} ref="form" autoComplete="off" noValidate={this.state.client}>
{this.state.form.render()}
<button type="submit" className="button">Next</button>
</form>
</div>
},
})
module.exports = NamePage
Here is NameTest.js :
//NameTest.js
var React = require('react')
var ReactAddons = require('react/addons')
var TestUtils = React.addons.TestUtils
var InputFieldItem = require('../../src/page/name/NamePage')
describe('Name page component', function(){
var renderedComponent;
before('render element', function() {
console.log("*** in before")
renderedComponent = TestUtils.renderIntoDocument(
<InputFieldItem />
);
});
it('Only First Name entered should display one error message', function() {
renderedComponent = TestUtils.renderIntoDocument(
<InputFieldItem />
);
var formElement = TestUtils.findRenderedDOMComponentWithTag(renderedComponent, 'form').getDOMNode()
var firstNameElement = TestUtils.scryRenderedDOMComponentsWithTag(renderedComponent, 'input')[0].getDOMNode()
var lastNameElement = TestUtils.scryRenderedDOMComponentsWithTag(renderedComponent, 'input')[1].getDOMNode()
var buttonElement = TestUtils.findRenderedDOMComponentWithTag(renderedComponent, 'button').getDOMNode()
TestUtils.Simulate.change(firstNameElement , {target: {value: 'Joe'}});
TestUtils.Simulate.submit(formElement)
var errorSpans = TestUtils.scryRenderedDOMComponentsWithClass(renderedComponent, 'error-message')
console.log("First name value is :|"+ firstNameElement.value + "|")
expect (errorSpans.length).to.equal(1)
})
});
I would like to iterate over this json string and input the values into a react component: {"users":[{"name":"jkhhjjh","url":"/users/240/individual_show"},{"name":"bob","url":"/users/241/individual_show"}]}
I can currently make one component from the first or second object within the json index but I cannot iterate over the whole string. My react-jsx code is below:
enter code here
var FriendInfo = React.createClass({
getInitialState: function() {
return {
url: '',
name: ''
};
},
componentDidMount: function() {
$.get(this.props.source, function(user) {
var users= user["users"][0];
if (this.isMounted()) {
this.setState({url: users.url,
url: users.url,
name: users.name
});
}
}.bind(this));
},
render: function() {
return (
<div className="friendInfo">
<img src="http://upload.wikimedia.org/wikipedia/commons/thumb/9/92/SRC-TV.svg/140px-SRC-TV.svg.png"></img>
<a href={this.state.url}>{this.state.name}</a>
</div>
);
}
});
var RequestLinks= React.createClass({
render: function() {
return (
<div className="requestLinks" style={{float:"right"}}>
accept
deny
</div>
);
}
});
var FriendBox = React.createClass({
render: function() {
return (
<div className="friendBox">
<FriendInfo source="/individual_relationships/show"/>
<RequestLinks />
</div>
);
}
});
React.render(<FriendBox />, document.getElementById('test'));`enter code here`
After the line var users= user["users"][0] I am able to insert the data into one iteration of the component. I would like to make a loop that would iterate over user["users"][0] and user["users"][1] to make two components on the page. How would I go about doing this?
You need to make use of map function which returns array of indivisual elements.
var FriendInfo = React.createClass({
getInitialState: function () {
return {
users: []
};
},
componentDidMount: function () {
$.get(this.props.source, function (user) {
var users = user["users"];
if (this.isMounted()) {
this.setState({users: users});
}
}.bind(this));
},
render: function () {
return (
<div>
{
this.state.users.map(function (user) {
return <div className="friendInfo">
<img src="http://upload.wikimedia.org/wikipedia/commons/thumb/9/92/SRC-TV.svg/140px-SRC-TV.svg.png"></img>
<a href={user.url}>{user.name}</a>
</div>
})
}
</div>
);
}
});
var RequestLinks = React.createClass({
render: function () {
return (
<div className="requestLinks" style={{float: "right"}}>
accept
deny
</div>
);
}
});
var FriendBox = React.createClass({
render: function () {
return (
<div className="friendBox">
<FriendInfo source="/individual_relationships/show"/>
<RequestLinks />
</div>
);
}
});
React.render(<FriendBox />, document.getElementById('test'));
I'm having a problem with an Ember computed property: It seems as though once the template gets updated, it stops listening to changes in the dependency property. But I don't understand why that would be the case.
Here's my template:
{{input type="text" value=searchText placeholder="Search for users..."}}
<br>
<ul>
{{#each user in searchResults}}
<li>{{user.Handle}}</li>
{{else}}
<p>No users found.</p>
{{/each}}
</ul>
And below is my controller:
App.AutocompleteController = Ember.Controller.extend({
searchText: null,
searchResults: function () {
var searchText = this.get('searchText');
var data = { 'searchTerm' : searchText };
var self = this;
alert("Calling searchResults");
if (!searchText) { return; }
if (searchText.length < 2) { return; }
$.get('/searchUsers', data).then(function (response) {
self.set("searchResults", JSON.parse(response));
}); //end then
}.property('searchText')
});
The first time searchResults actually makes an AJAX call and returns data, the autocomplete results get populated, but after that, searchResults doesn't get called again until I refresh the client.
NEVER MIND. It's right there in the code. On a successful ajax code, I'm reassigning searchResults to a static array, no longer a function.
Returning won't work out of a .then, however, so I still need a workaround for returning the data. For that, I will add a more traditional Ember event listener to call my 'search' function which will reset the property of 'searchResults'
New template:
{{input type="text" value=searchText placeholder="Search for users..." action=search on='change'}}
<ul>
{{#each user in searchResults}}
<li>{{user.Handle}}</li>
{{else}}
<p>No users found.</p>
{{/each}}
</ul>
New controller:
App.AutocompleteController = Ember.Controller.extend({
searchText: null,
search: function () {
var searchText = this.get('searchText');
var data = { 'searchTerm' : searchText };
var self = this;
if (!searchText) { return; }
if (searchText.length < 2) { return; }
else {
$.get('/searchUsers', data).then(function (response) {
self.set("searchResults", JSON.parse(response));
}); //end then
}
}.property('searchText')
});