how to make ngReact work with ng-directives (ngClick, ngStyle, etc.) - angularjs

[UPDATE July 23 2015]
I want to create a list of buttons, one for each marker in ctrl.getMarkers(). Let's assume a marker is something like
marker: {'title':' '}.
Using the classic ng-repeat, I have the following
<div ng-repeat="marker in ctrl.getMarkers()" >
<button class="btn btn-default"
ng-click = "doSomething(marker)"
ng-style="ctrl.isRed() ? {color:red} : {color:black}">
<b> {{::marker.title}}</b>
</button>
</div>
I have about 100 buttons to show, but the update is not fast at all. So I want to try the ng-react library. Unfortunately I haven't well understood how to use it.
So far, I wrote this:
HTML
<poi-list list="ctrl.getMarkers()" />
JS
/** #jsx React.DOM */
.value( "PoiList", React.createClass( {
propTypes : {
list: React.PropTypes.object.isRequired
},
getDefaultProps: function() {
return { list: [] };
},
render: function()
{
var markers = this.props.list.map( function( marker, i )
{
return React.DOM.button({className:'btn btn-default'
/*ng-click? ng-class?*/
}, marker.title) ;
} );
return React.DOM.div(null, markers);
}
}))
.directive( 'poiList', function( reactDirective ) {
return reactDirective( 'PoiList' );
} );
How could I use ng-click, ng-class etc with the React DOM element?
Here's the JSFiddle
[UPDATE 2]
I found a solution and I've answered my own question below. However I've got still a problem slowing down react: "Deprecated attempt to access property 'nodeType' on a non-Node object". Please, look at my answer below for further info about the error. Thank you

I figured out how to (almost) solve my problem. I pass the controller as attribute of the react element, so I can invoke its functions. Like this:
HTML
<poi-list list="ctrl.getMarkers()" ctrl="ctrl"/>
JS
.value( "PoiList", React.createClass( {
propTypes : {
list: React.PropTypes.object.isRequired,
ctrl: React.PropTypes.object.isRequired
},
render: function()
{
var ctrl = this.props.ctrl; // directive's Controller
var markers = this.props.list.map( function( marker, i )
{
return React.DOM.button( {
className:'btn btn-default',
onClick: function(){ctrl.doSomething(marker);},
onMouseOver: function(){ctrl.doSomethingElse(marker);},
}, marker.title
) ;
} );
return React.DOM.div(null, markers);
}
}))
.directive( 'poiList', function( reactDirective ) {
return reactDirective( 'PoiList' );
} );
It works!! JSFiddle
[UPDATE 1]
Anyway.... in my real application it's terrible slow... much slower then ng-repeat. I don't know why. Looking at the console, I have a bunch of error "Deprecated attempt to access property 'nodeType' on a non-Node object" from angular.js:328, and I guess this is the reason of the slow performance. I don't know what's the problem, and I don't have this error in the JSFiddle... any help?
This is the piece of Angular code that rises the error:
function forEach(obj, iterator, context) {
var key, length;
if (obj) {
if (isFunction(obj)) {
for (key in obj) {
// Need to check if hasOwnProperty exists,
// as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function
if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) {
iterator.call(context, obj[key], key, obj);
}
}
/*---->*/ } else if (isArray(obj) || isArrayLike(obj)) {// <---ERR
var isPrimitive = typeof obj !== 'object';
for (key = 0, length = obj.length; key < length; key++) {
if (isPrimitive || key in obj) {
iterator.call(context, obj[key], key, obj);
}
}
} else if (obj.forEach && obj.forEach !== forEach) {
obj.forEach(iterator, context, obj);
} else {
for (key in obj) {
if (obj.hasOwnProperty(key)) {
iterator.call(context, obj[key], key, obj);
}
}
}
}
return obj;
}
[UPDATE 2]
I figured out that the problem was the array of complex objects passed to the React Element. It's impossible for React making comparison between old and new object, resulting in slowness and errors. This is the explanation of this problem and the solution.

Related

Protractor: How to find an element in an ng-repeat by text?

I'm looking to get a specific element inside an ng-repeat in protractor by the text of one of its properties (index subject to change).
HTML
<div ng-repeat="item in items">
<span class="item-name">
{{item.name}}
</span>
<span class="item-other">
{{item.other}}
</span>
</div>
I understand that if I knew the index I wanted, say 2, I could just do:
element.all(by.repeater('item in items')).get(2).element(by.css('.item-name'));
But in this specific case I'm looking for the 'item in items' that has the specific text (item.name) of say "apple". As mentioned, the index will be different each time. Any thoughts on how to go about this?
public items = element.all(by.binding('item.name'))
getItemByName(expectedName) {
return this.items.filter((currentItem) => {
return currentItem.getText().then((currentItemText) => {
return expectedName === currentItemText;
});
}).first();
}
And invoke method like that this.getItemByName('Item 1'). Replace Item 1 with expected string.
function elementThere(specificText, boolShouldBeThere){
var isThere = '';
element.all(by.repeater('item in items')).each(function (theElement, index) {
theElement.getText().then(function (text) {
// Uncomment the next line to test the function
//console.log(text + ' ?= ' + specificText);
if(text.indexOf(specificText) != -1){
element.all(by.repeater('item in items')).get(index).click();
isThere = isThere.concat('|');
}
});
});
browser.driver.sleep(0).then(function () {
expect(isThere.indexOf('|') != -1).toBe(boolShouldBeThere);
});
}
it('should contain the desired text', function () {
elementThere('apple', true);
}
Does this fit your needs?
I was able to solve this by simplifying #bdf7kt's proposed solution:
element.all(by.repeater('item in items')).each(function(elem) {
elem.getText().then(function(text) {
if(text.indexOf('apple') != -1) {
//do something with elem
}
});
});
Also, this particular solution doesn't work for my use case, but I'm sure will work for others:
var item = element(by.cssContainingText('.item-name', 'apple'));
//do something with item

Maximum call stack exceeded error in ReactJS. Can someone help explain what's going on? (Snippet on JSFiddle)

I'm new to ReactJS and was trying my hands on a simple project. Basically, the snippet create a list of friends from an array and displays the total number of friends.
For some reason, I realized the incrementFriendsCount function throws a "Maximum call stack exceeded error" when I add a new friend
The code snippet below is also available on JSFiddle.
var HelloUser = React.createClass({
getInitialState: function() {
return {
name: "Toyosi",
friends: ["Susanna", "Jibola", "Worreva"],
friendsCount: 0
}
},
addFriends: function(friend) {
this.setState({
friends: this.state.friends.concat([friend])
});
},
componentWillMount: function() {
this.setState({
friendsCount: this.state.friends.length
});
},
incrementFriendsCount: function() {
this.setState({
friendsCount: this.state.friends.length
});
},
render: function() {
return ( < div >
Villain: {
this.state.name
}, No of friends: {
this.state.friendsCount
} < br / >
< AddingTheFriend addNew = {
this.addFriends
}
incCount = {
this.incrementFriendsCount
}
/>
<ListFriends enemies={this.state.friends} / >
< /div>
);
}
});
var ListFriends = React.createClass({
propTypes: {
enemies: React.PropTypes.array.isRequired
},
render: function() {
var allFriends = this.props.enemies.map(function(friend){
return <li>{friend}</li > ;
});
return ( < div > Her evil friends:
< ul > {
allFriends
} < /ul>
</div >
)
}
});
var AddingTheFriend = React.createClass({
getInitialState: function() {
return {
newFriend: ''
}
},
propTypes: {
addNew: React.PropTypes.func.isRequired
},
updateNewFriend: function(change) {
this.setState({
newFriend: change.target.value
});
},
addTheFriend: function() {
this.props.addNew(this.state.newFriend);
this.setState({
newFriend: ''
})
},
componentWillReceiveProps: function() {
this.props.incCount();
},
render: function() {
return ( < div >
< input type = "text"
value = {
this.state.newFriend
}
onChange = {
this.updateNewFriend
}
/>
<button type="button" onClick={this.addTheFriend}>Add Friend</button >
< /div>
)
}
});
React.render(<HelloUser / > , document.getElementById('app'));
<script src="http://fb.me/react-js-fiddle-integration.js"></script>
<div id="app"></div>
I will appreciate if anyone could throw more light on why this error is thrown.
You are calling this.props.incCount in componentWillReceiveProps which sets the state of the parent component and the effect will be that AddingTheFriend is rendered again, and this.props.incCount is called again. Hence the stack overflow.
Another advice would be that generally you want to be careful and use setState as little as possible, in as few components as possible. Simply increment the friends count at the same time you concat the new friend to parent component's state.
Here's the codepen --much better than JSFiddle in my opinion.
As a future reference for people with the same error output when using react:
This error will also occur of you run your app with both
webpack-dev-server --hot
new webpack.HotModuleReplacementPlugin()
So: When you run your server with --hot and also have the hotModuleReplacementPlugin enabled in your webpack config then you will get into the situation that your components will recursively update, thus generating exactly the error message the OP mentioned.
Simple solution: Omit one of the two things, e.g. omit the "--hot" since you already use the plugin for that.
In my case it was an infinite loop, or if you like an obvious logical error.

ng-class - finding a value inside object

I have an object that looks like this:
$scope.things = [
{
name: 'Bob!',
short_name: 'bob',
info: 'something something'
},
{
name: 'Steve',
short_name: 'steve',
info: 'something something something'
},
];
I loop through them like this and add an ng-click:
<div ng-repeat="thing in things" ng-click="addThing(thing.name, thing.short_name, thing_info" ng-class="thingClass(thing.name)">content goes here</div>
the ng-click="addThing()" basically bunches up the values and adds them to the object.
When clicked, it should add the class selected - this worked fine and dandy when I wasn't using a multidimensional object, because it was simply looking for name inside the object / array (at this point, I think it's an object... but at the time, it was an array)
I can't work out how to do the equivalent to this...
$scope.thingClass= function(name) {
if($scope.thingSelected.indexOf(name) != -1) {
return 'selected';
}
};
...with the object as it now stands. I've tried to adapt a few answers from here that I found through google, such as:
$scope.teamClass = function(name) {
var found = $filter('filter')($scope.thingSelected, {id: name}, true);
if (found.length) {
return 'selected';
}
};
...but with no joy.
Can anyone point / nudge me in the right direction?
You could simply pass the thing object to thingClass:
... ng-class="thingClass(thing)" ...
and implement thingClass as follows:
$scope.thingClass= function(thing) {
return $scope.thingSelected.indexOf(thing) >= 0 ? 'selected' : '';
}
And maybe your should apply this technique to addThing also:
... ng-click="addThing(thing)" ...
$scope.addThing = function(thing) {
if ($scope.thingSelected.indexOf(thing) < 0)
$scope.thingSelected.push(thing);
}
But instead of tracking the selected things in an array its much easier to introduce a selected property in each thing:
$scope.addThing = function(thing) {
thing.selected = true;
}
$scope.thingClass= function(thing) {
return thing.selected ? 'selected' : '';
}

kendo ui get id of checkbox when unchecked

i am using kendo ui tree view with check box
i want the check box's id when it is getting unchecked
this is kendo ui mine code
// var homogeneous contains data
$("#treeview").kendoTreeView({
checkboxes: {
checkChildren: false,
template:"# if(!item.hasChildren){# <input type='hidden' id='#=item.id#' parent_id='#=item.parent_id#' d_text='#=item.value#'/> <input type='checkbox' id_a='#= item.id #' name='c_#= item.id #' value='true' />#}else{# <div id='#=item.id#' style='display:none;' parent_id='#=item.parent_id#' d_text='#=item.value#'/> #}#",
},
dataSource: homogeneous,
dataBound: ondata,
dataTextField: "value"
});
function ondata() {
//alert("databound");
}
// function that gathers IDs of checked nodes
function checkedNodeIds(nodes, checkedNodes) {
//console.log(nodes);
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].checked) {
checkedNodes.push(nodes[i].id);
}
if (nodes[i].hasChildren) {
checkedNodeIds(nodes[i].children.view(), checkedNodes);
}
}
}
// show checked node IDs on datasource change
$("#treeview").data("kendoTreeView").dataSource.bind("change", function() {
var checkedNodes = [],
treeView = $("#treeview").data("kendoTreeView"),
message;
checkedNodeIds(treeView.dataSource.view(), checkedNodes);
if (checkedNodes.length > 0) {
message = "IDs of checked nodes: " + checkedNodes.join(",");
} else {
message = "No nodes checked.";
}
$("#result").html(message);
});
in this code i am not getting checkbox's id when it is unchecked so i have tried this
jquery code
$('input[type=checkbox]').click(function() {
if($(this).is(':checked')) {
alert('checked');
} else {
alert('not checked');
}
});
this code is only working in js fiddle but not in my case http://jsfiddle.net/NPUeL/
if i use this code then i can get the number of count but i dont know how to use it
var treeview = $("[data-role=treeview]").data("kendoTreeView");
treeview.dataSource.bind("change", function (e) {
if (e.field == "checked") {
console.log("Recorded Selected: " + $("[data-role=treeview] :checked").length);
}
});
what changed i need to do in data source so i can get id
thanks in adavance
If you want to get the id you might do:
$('input[type=checkbox]').click(function (e) {
var li = $(e.target).closest("li");
var id = $("input:hidden", li).attr("id");
var node = treeView.dataSource.get(id);
if (node.checked) {
console.log('checked');
} else {
console.log('not checked');
}
});
What I do in the event handler is:
find the closest li element that is the node of the tree that has been clicked.
the id is in an HTML input element that is hidden (this is the way that I understood that you have stored it).
Get item from dataSource using dataSource.get method.
See your code modified and running here
i made the small change and its working now
function ondata() {
$('input[type=checkbox]').click(function() {
if($(this).is(':checked')) {
alert('checked');
} else {
alert('not checked');
}
});
}

Error when using BreezeJS with AngularJS

Using BreezeJS with AngularJS gives errors. For example when using 'filter' in an ng-repeat the console reports: running out of stack space.
Steps to reproduce
Take the Todo-Angular BreezeJS sample & open in VS 2012.
In index.html add right before the <ul>
<div>
<input ng-model="query" type="search" placeholder="search" />
</div>
Add the following code to the data-ng-repeat on the li element
<li data-ng-repeat="item in items | filter:query">
The filter:query should filter the list based upon the text in the input
but it doesn't.
In IE 10 the console reports "running out of stack space".
In Chrome the console reports"Range Error":
(anonymous function) angular.js:5582
(anonymous function) angular.js:4679
Scope.$digest angular.js:7739
Scope.$apply angular.js:7926
listener angular.js:11228
v.event.dispatch jquery-1.8.3.min.js:2
o.handle.u
When you use angular.copy(src, dest); where src is created by BreezeJS I see
another stack_overflow error.
That won't work because you're asking Angular to match the search text to every property of the TodoItem.
A Breeze entity's properties include the entityAspect which has a property called entity that points back to the TodoItem instance ... and around you go until the stack overflows (pun intended).
You need to use a filter function that does specific comparisons. Try this:
In Index.html
<div>
<input data-ng-model="searchText" type="Search" placeholder="search" />
</div>
<ul>
<li data-ng-repeat="item in items | filter:itemFilter">
... etc. ...
In controller.js
$scope.searchText = "";
// Beware: this is called a lot!
$scope.itemFilter = function (todoItem) {
var searchText = $scope.searchText;
// if there is search text, look for it in the description; else return true
return searchText ?
-1 != todoItem.Description.toLowerCase().indexOf(searchText.toLowerCase()) :
true;
};
Works like a charm on my machine :)
p.s.: your mishap with angular.copy() has the same cause ... it does a depth copy of every property and entities tend to have circular references.
Ok, i implemented a custom filter, that excludes navigation properties. It just filters in the currently received breeze entity.
(function () {
'use strict';
angular.module('appFilter', []).filter('breezeFilter', function () {
function contains(searchString, searchTerm) {
if (!searchString) return false;
return searchString.toString().toLowerCase().indexOf(searchTerm.toLowerCase()) != -1;
}
function getKeys(entity) {
var names = [];
var properties = entity.entityAspect.extraMetadata.properties;
for (var property in properties) { names.push(property); }
return names;
}
function search(entity, searchTerm) {
var found = false;
if ("entityAspect" in entity) {
var keys = getKeys(entity);
for (var i = 0; i < keys.length && !found; i++) {
if (keys[i].slice(-2) !== "Id") {
var obj = entity[keys[i]];
switch (typeof obj) {
case 'object':
if (obj && !('navigationProperty' in obj)) {
found = search(obj, searchTerm);
}
break;
case 'number':
case 'string':
found = contains(obj, searchTerm);
break;
case 'boolean':
default:
}
}
}
}
return found;
}
return function (breezeEntities, expression) {
if (!expression || expression.length < 2) return breezeEntities;
var filtered = [];
angular.forEach(breezeEntities, function (entity) {
if (search(entity, expression)) {
filtered.push(entity);
}
});
return filtered;
}
});
})();
I hope this helps.
I just had the same problem and I solved by using query projections in breeze, i.e. the "select" clause. It returns pure JavaScript object as opposed to the "wrapped" breeze entities.

Resources