Loop through object with unkown levels of children in Preact - preact

I'm trying to loop through an object with children, that can have children, that can have children.
Basically I won't know how many child elements and levels the object can contain. Is there an easy way to loop through these, and render out a component in a list view without having to call the Object.keys(myobject).map many times in side eachother. Inside the render function?
parent: {
term: "cats",
children: {
child: {
term: "kids",
otherdata: "dekme",
children: {
granddaughter: {term: "kids", otherdata: "dekme"},
grandson: {term: "mip", some other data: "si"}
}
},
}
}
And also the output should be something like:
<ul>
<li>
Parent
<ul>
<li>child</li>
<li>child
<ul>
<li>grandkid
</li>
</ul>
</li>
</ul>
</li>
</ul>

Great question! This class of problem is generally referred to as walking / tree walking, and there are probably some great libraries on npm that you could use to make it easier. We can write a simple recursive one by hand though. I'll assume your nested objects containing child elements look something like this:
var treeOfObjects = {
children: [
{
name: 'billy'
},
{
name: 'wanda',
children: [
{
name: 'stanley'
}
]
}
]
};
Here's a walk function that will crawl that tree, call an iterator function you supply on each child it finds, and collect up the results in an Array.
function walk(object, iterator, results) {
// no results array it means we're at the root.
// create the shared array, then map the root through our iterator.
if (!results) {
results = [iterator(object, null, object)];
}
if (object.children) {
for (let i=0; i<object.children.length; i++) {
// call the iterator on this child and append the result:
let value = object.children[i];
results.push(iterator(value, i, object));
// now "walk into" the child object (appending its children, etc):
walk(value, iterator, results);
}
}
return results;
}
Here's what it looks like in practice:
class Foo extends Component {
render({ someTreeOfObjects }) {
return (
<ul>
{walk(someTreeOfObjects, object => (
<li>{object.name}</li>
))}
</ul>
);
}
}
// render(<Foo someTreeOfObjects={treeOfObjects} />)
// .. produces:
// <ul>
// <li>billy</li>
// <li>wanda</li>
// <li>stanley</li>
// </ul>
Update - for nested output:
function nodeToListItem (object) {
// if we have children, we'll recurse into them
const children = object.children || [];
return (
<li>
{object.name}
{children.length>0 && (
<ul>
{children.map(nodeToListItem)}
<ul>
)}
</li>
);
}
class Foo extends Component {
render({ someTreeOfObjects }) {
return (
<ul>
{nodeToListItem(someTreeOfObjects)}
</ul>
);
}
}
Because the nesting from the original structure matches what we want to produce in the DOM, this ends up being a simple graph transformation.

Related

How to get the v-model of component that called the function in Vuetify?

I happen to do the form in which each text-field has to cooperate with each other for example:
<template>
<v-app>
<v-text-field v-model="foo1" #input="updateForm">
<v-text-field v-model="foo2" #input="updateForm">
</v-app>
</template>
<script>
export default {
data() {
return {foo1:0, foo2:0}
},
methods:{
updateForm(foo){
foo1=foo1/foo1+foo2
foo2=foo2/foo1+foo2
//Can we get the v-model of foo which called the function to make a special update?? like
// foo=foo/2
}
}
}
</script>
Im using Vue2
Using an array to hold all the values of your inputs and passing the array index to the event handler method is the most common way of solving your problem. With an array you can also utilize v-for to dynamically render your input elements, which cuts down on duplicate code.
<template>
<v-app>
<v-text-field
v-for="(foo, i) in foos" :key="i"
type="number"
v-model.number="foos[i]"
#input="updateForm(i)"
/>
</v-app>
</template>
<script>
export default {
data() {
return {
foos: [0, 0]
};
},
methods: {
updateForm(fooIndex) {
this.foos[fooIndex] += 1;
}
}
};
</script>

how can injection dynamic html element to page with next.js?

how can dynamic injection html element to page with next.js? that these elements Unknown type like(input, checkbox, img,...). this element specified with api that return json type like this:
[{
"id":"rooms",
"title":"Rooms",
"order":1,
"type":"string",
"widget":"select",
"data":[{
"Id":18,
"ParentId":null,
"Title":"One",
"Level":null,
"Childrens":[]
},
{"Id":19,
"ParentId":null,
"Title":"Two",
"Level":null,
"Childrens":[]
},
{"Id":20,
"ParentId":null,
"Title":"Three",
"Level":null,
"Childrens":[]
}]
},
{
"id":"exchange",
"title":"Exchange",
"order":0,
"type":"boolean",
"widget":"checkbox",
"data":[]
}]
my try is:
Index.getInitialProps = async function({req, query}) {
const res= await fetch('url api')
var elements= await res.json()
var test = () => (
<div>
{...... convert json to html elements.......}
</div>
)
return {
test
}
})
function Index(props) {
return(
<a>
{props.test}
</a>
)
}
result is null, mean nothing for presentation.
the question is, Do I do the right thing? Is there a better way?
What happens is that during the transfer of props from server to client in getInitialprops, JSON is serialized and so functions are not really serialized. See https://github.com/zeit/next.js/issues/3536
Your best bet is to convert the test data into a string of HTML data and inject it using dangerouslySetInnerHTML. An example will be:
class TestComponent extends React.Component {
static async getInitialProps() {
const text = '<div class="homepsage">This is the homepage data</div>';
return { text };
}
render() {
return (
<div>
<div className="text-container" dangerouslySetInnerHTML={{ __html: this.props.text }} />
<h1>Hello world</div>
</div>
);
}
}
The catch with this is that the string you return must be a valid HTML (not JSX). So notice I used class instead of className
You can read more about it here: https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml

Array.map inside Array.map is not working

I am getting an array of object returned from Database.I want to map that array and inside that i want to map another array.But i am getting some error. If anyone knows please help out with this problem.
displayMovies(){
if(!this.state.body) return ( <h2> Loading.... </h2>)
else{
return this.state.body.map((data) => {
return(
<div key={data._id}>
<li> {data.name} </li>
<ul>
{
return data.map((cast) => {
return <li> cast.name </li>
})
}
</ul>
</div>
)
})
}
}
Data response
You are not in any function inside nested loop so you do not need to return any thing
Replace
return data.map((cast) => {
to
data.map((cast) => {
and add apply map function on cast property of data
displayMovies(){
if(!this.state.body) return ( <h2> Loading.... </h2>)
else{
return this.state.body.map((data) => {
return(
<div key={data._id}>
<li> {data.name} </li>
<ul>
{
data.cast.map((cast) => {
return <li> {cast.name} </li>
})
}
</ul>
</div>
)
})
}
}
Your first map function returns an Object. Objects don't have map function use a for in loop instead, inside the loop, check if the property is an object.
Replace second map with something like this :
for (const key in object) {
if (object.hasOwnProperty(key) && typeof(object[key]=='object') {
// do something with object[key].name
}
}

Vue.js - Pagination

I have the following pagination component.
If users adds remove items dynamically i.e via some ajax call, how do i ensure the correct active or disabled classes are applied on the pagination links?
For example if the user is currently on the last page which only has 1 item, if the user deletes that item, the pagination links re-render but then i lose the active disable class becuase that page no longer exists. i.e. the links should update to move the user to previous page.
<div class="comment-pager ">
<div class="panel panel-default panel-highlight no-border ">
<div class="panel-body padded-5">
<nav v-if="totalItems > pageSize">
<ul class="pagination">
<li v-bind:class="[currentPage == 1 ? disabled : '']">
<a v-on:click.prevent="previous()" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<li v-bind:class="[currentPage == pages ? active : '']" v-for="page in pages" v-on:click.prevent="changePage(page)">
<a>{{ page }}</a>
</li>
<li v-bind:class="[currentPage == pages.length ? disabled : '']">
<a v-on:click.prevent="next()" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['totalItems', 'pageSize']
data: function () {
return {
currentPage: 1,
pages: [],
}
},
watch: {
totalItems: function () {
var pagesCount = Math.ceil(this.totalItems / this.pageSize);
this.pages = [];
for (var i = 1; i <= pagesCount; i++)
this.pages.push(i);
}
},
methods: {
changePage: function (page){
this.currentPage = page;
this.$emit('pageChanged', page);
}
previous: function (){
if (this.currentPage == 1)
return;
this.currentPage--;
this.$emit('pageChanged', this.currentPage);
}
next: function () {
if (this.currentPage == this.pages.length)
return;
this.currentPage++;
this.$emit('pageChanged', this.currentPage);
}
},
}
</script>
<paginator v-bind:total-items="totalItems" v-bind:page-size="query.pageSize" v-on:pageChanged="onPageChange"></paginator>
There is no complete equivalent to ngOnChanges() in vue.
ngOnChanges() is a lifecycle hook which takes in an object that maps each changed property name to a SimpleChange object holding the current and previous property values.
If you want the lifecycle hook that gets invoked after every change in data and before re-rendering the virtual DOM then you should be using beforeUpdate() hook.
But as in ngOnChanges() you can't get the hold of which property is updated or what is it's oldvalue or newValue is.
As mklimek answered you can set up watcher on the properties you want to watch for changes.
In watcher you get what the oldValue is and what it's changed new value is
new Vue({
el:'#app',
data:{
prop1: '',
prop2: '' // property to watch changes for
},
watch:{
prop#(newValue, oldValue){
console.log(newValue);
console.log(oldValue);
}
}
});
EDIT
For your case you do not need a watcher. You can setup the pages[] property as a computed property:
computed:{
pages(){
var pageArray = [];
var pagesCount = Math.ceil(this.totalItems / this.pageSize);
for (var i = 1; i <= pagesCount; i++)
pages.push(i);
}
return pageArray;
}
computed properties are cached based on their dependencies. A computed property will only re-evaluate when some of its dependencies have changed in your case the props
totalItems and pageSize
Now you can use the pages computed property as normal data property
You probably want to use watch property of a Vue instance.
var vm = new Vue({
data: {
count: 1
},
watch: {
count: function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
}
})

AngularJS : How to say to a directive to clone scope?

I have this fiddle, and can not make this work. I believe that the reason resides in that two li elements with a custom directive edit-in-place share scope.
The solution would be to say to the directive to create a copy of the scope that binds on the parent - can transclude help?
angular.module('bla', [])
.directive('editInPlace', ['$parse','$compile', function($parse, $compile) {
return {
restrict: 'A',
scope: true,
link: function (scope, element, attribs) {
var inputStart = '<input style="border: 2 solid black" name="inPlaceInput" style="display:none" value="';
var inputEnd = '">';
scope.editModeAccessor = $parse(attribs.editInPlace);
scope.modelAccessor = $parse(attribs.ngBind);
scope.$watch(attribs.editInPlace, function(newValue, oldValue){
if (newValue){
console.debug("click");
console.debug("value: " + scope.modelAccessor(scope));
var inputHtml = inputStart + scope.modelAccessor(scope) + inputEnd;
element.after(inputHtml);
jQuery(element).hide();
scope.inputElement = jQuery("input[name=inPlaceInput]");
scope.inputElement.show();
scope.inputElement.focus();
scope.inputElement.bind("blur", function() {
blur();
});
} else {
blur();
}
});
function blur(){
console.debug("blur secondary");
if (scope.inputElement){
console.debug("blur secondary inputElement found");
var value = scope.inputElement.val();
console.debug("input value: "+ value);
scope.inputElement.remove();
jQuery(element).show();
scope.editModeAccessor.assign(scope, false);
scope.modelAccessor.assign(scope, value);
}
}
}
}
}]);
function ContactsCtrl($scope, $timeout){
$scope.contacts = [{number:'+25480989333', name:'sharon'},{number:'+42079872232', name:''}];
$scope.editMode = false;
var editedId;
$scope.edit = function(id){
$scope.editMode = true;
jQuery("#"+id).hide();
editedId = id;
//TODO show delete button
}
$scope.$watch('editMode', function(newValue, oldValue){
if (!newValue && editedId){
jQuery("#"+editedId).show();
}
});
}
<div ng-app="bla">
<div ng-controller="ContactsCtrl">
<h4>Contacts</h4>
<ul>
<li ng-repeat="contact in contacts">
<span edit-in-place="editMode" ng-bind="contact.number"></span>
<span edit-in-place="editMode" ng-bind="contact.name"></span>
<span id="{{$index}}" ng-click="edit($index)"><i class="icon-edit">CLICKtoEDIT</i></span>
</li>
</ul>
</div></div>
I think cloning the scope is not the best solution.
When creating a directive in angular, you should encapsulate all the functionality within the directive. You should also avoid mixing jQuery in when you don't have to. Most of the time (as in this case) you're just introducing unnecessary complexity. Lastly, classes are the best way of controlling display, rather than the style attribute on an element.
I took the liberty of rewriting your directive in a more "angular" way - with no jQuery. As you can see from the updated jsFiddle, it is simpler and cleaner. Also, it works!
This directive can be easily modified to add lots of additional awesome functionality.
app.directive( 'editInPlace', function() {
return {
restrict: 'E',
scope: { value: '=' },
template: '<span ng-click="edit()" ng-bind="value"></span><input ng-model="value"></input>',
link: function ( $scope, element, attrs ) {
// Let's get a reference to the input element, as we'll want to reference it.
var inputElement = angular.element( element.children()[1] );
// This directive should have a set class so we can style it.
element.addClass( 'edit-in-place' );
// Initially, we're not editing.
$scope.editing = false;
// ng-click handler to activate edit-in-place
$scope.edit = function () {
$scope.editing = true;
// We control display through a class on the directive itself. See the CSS.
element.addClass( 'active' );
// And we must focus the element.
// `angular.element()` provides a chainable array, like jQuery so to access a native DOM function,
// we have to reference the first element in the array.
inputElement[0].focus();
};
// When we leave the input, we're done editing.
inputElement.prop( 'onblur', function() {
$scope.editing = false;
element.removeClass( 'active' );
});
}
};
});

Resources