I'm using AlloyUI components within a YUI script and am trying to combine aui-tabview (Pills) with aui-pagination such that clicking on each tab(pill) updates the pagination from the contents/nodelist for each tab. For example, if there are 7 items in the nodelist for tab-2 then I should get 7 pagination buttons, 6 items for tab-3 should show 6 pagination buttons, etc. I cannot get these two components to integrate. Any help would be gratefully received.
Here is my code:
<div id="myTab">
<ul class="nav nav-pills">
<li class="active">View all</li>
<li>Beauty</li>
<li>Days out</li>
<li>Holidays</li>
</ul>
<div class="products tab-content">
<div id="beauty" class="tab-pane">
<div>some content</div>
<div>some more content</div>
<div>more content</div>
<div>a few words</div>
</div>
<div id="days-out" class="tab-pane">
<div>some content</div>
<div>some more content</div>
<div>more content</div>
<div>a few words</div>
</div>
<div id="holidays" class="tab-pane">
<div>some content</div>
<div>some more content</div>
<div>more content</div>
<div>a few words</div>
</div>
</div>
</div>
<script>
YUI({
}).use('node', 'node-base', 'event', 'transition', 'anim', 'aui-tabview', 'aui-pagination', function(Y) {
new Y.TabView(
{
srcNode: '#myTab',
type: 'pills'
}
).render();
Y.one(".nav.nav-pills").delegate('click', function(e) {
var id = Y.one(e.currentTarget);
var href = id.get('href');
var arr = href.split("#");
var target = arr[1];
var pages = Y.all('#' +target + " > div");
var total_rows = pages._nodes.length;
Y.log(total_rows);
new Y.Pagination(
{
page: 1,
total: total_rows,
boundingBox: '#pagination',
circular: false,
contentBox: '#pagination .pagination-content',
on: {
changeRequest: function(event) {
var instance = this,
current = event.currentTarget,
state = event.state,
lastState = event.lastState;
if (lastState) {
pages.item(lastState.page - 1).setStyle('display', 'none');
}
pages.item(state.page - 1).setStyle('display', 'block');
}
},
after: {
changeRequest: function(event) {
// goto top
a = new Y.Anim(
{
node: 'body',
to: {scrollTop: 0},
duration: 0.4,
easing: Y.Easing.easeOut
}
);
a.run();
}
},
strings: {
next: '»',
prev: '«'
}
}
).render();
}, 'a');
}
);
</script>
Since I've received no answer to the above posting, I've been left with a few days to mull it over and come up with the following solution. Any additions or improvements welcome.
<script>
YUI({}).ready('node', 'event', 'transition', 'anim', 'aui-tabview', 'aui-pagination', function(Y) {
var tabs = '';
var setup = '';
var tabTargetID = '';
tabs = new Y.TabView(
{
srcNode: '#myTab',
type: 'pills'
}
).render();
setup = {
contentId: function() {
if (tabTargetID !== '') {
var content = tabTargetID;
} else {
var id = tabs.getActiveTab();
var href = id.one('a').get('href');
var arr = href.split("#");
var content = '#' + arr[1];
}
return content;
},
pages: function() {
return Y.all(setup.contentId() + " > div");
},
currentTabPageTotal: function() {
return setup.pages()._nodes.length;
}
};
var pages = setup.pages();
var pg = new Y.Pagination(
{
page: 1,
total: setup.currentTabPageTotal(),
boundingBox: '#pagination',
circular: false,
contentBox: '#pagination > ul',
offset: 1,
on: {
changeRequest: function(e) {
var instance = this,
state = e.state,
lastState = e.lastState;
// Set the pagination links active state
Y.all('.pagination > ul > li:not(.pagination-control)').removeClass('active');
var pg_links = Y.all(".pagination > ul > li:not(.pagination-control)");
pg_links.item(state.page - 1).addClass('active');
// Hide all but the focussed tabs 1st paginated page
pages.setStyles({display: 'none', opacity: '0'});
pages.item(state.page - 1).setStyle('display', 'block')
.transition({
opacity: {value: 1, duration: 1}
});
}
},
after: {
changeRequest: function(e) {
// goto top
a = new Y.Anim(
{
node: 'body',
to: {scrollTop: 0},
duration: 0.4,
easing: Y.Easing.easeOut
}
);
a.run();
}
},
strings: {
next: '»',
prev: '«'
}
}
).render();
tabs.on('selectionChange', function(e) {
var tabIndex = tabs.indexOf(e.newVal);
var tabContents = Y.all(".tab-content > div");
var tabTarget = Y.one(tabContents.item(tabIndex));
tabTargetID = '#' + tabTarget.get('id');
pages = setup.pages();
// Hide all but the focussed tabs 1st paginated content
Y.all(tabTargetID + ' > div').setStyles({display: 'none', opacity: '0'});
Y.one(tabTargetID + ' > div').setStyles({display: 'block'})
.transition({
opacity: {value: 1, duration: 1}
});
// For the focussed Tab, build the pagination links with x number of links and set to 1st page
pg.setAttrs({page: 1, total: setup.currentTabPageTotal()});
// Highlight the 1st pagination link
Y.all('.pagination > ul > li:not(.pagination-control)').removeClass('active');
Y.one('.pagination > ul > li:not(.pagination-control)').addClass('active');
});
});
</script>
Related
I am trying to paginate one of my pages in the application which is built with React / NextJs - getServerSideProps.
Step 1: Creates a pagination component
Step 2: Redirect to a URL with Page numbers (based on user clicks)
Step 3: It should re-render getServerSideProps with the newer page value, which is not happening right now.
My current code block (Server Side Props - API call):
export const getServerSideProps = async (ctx) => {
try {
const APIKey = await getCookieAPIKey(ctx);
const user = await getCookieUser(ctx);
const dataSSR = await getDataSSR(
APIKey,
'/xyz/xyz/read/',
user.user_id,
'user_id',
ctx.query.page,
ctx.query.limit
);
// console.log(d, "data")
return {
props: {
dataSSR
}
};
} catch (err) {
...
return { props: { fetchError: err.toString() } };
}
};
export const getDataSSR = async (APIKey, path, id, idString, page, limit) => {
//generate URL path for fetch
const base_url = `${ENDPOINT}/services`;
let url;
if (id && !idString && !page) {
url = base_url + path + '?key=' + APIKey + '&id=' + id;
} else if (id && idString && page) {
url = base_url + path + '?key=' + APIKey + `&${idString}=` + id + '&page=' + page + `&limit=${!limit ? '24' : limit}`;
} else if (id && idString && !page) {
url = base_url + path + '?key=' + APIKey + `&${idString}=` + id + '&page=0' + `&limit=${!limit ? '24' : limit}`;
}
else {
url = base_url + path + '?key=' + APIKey + '&page=' + page + `&limit=${!limit ? '10' : limit}`;
}
I followed this tutorial for pagination.
With a modification of the click method statement:
<ReactNextPaging
itemsperpage={itemsperpage}
nocolumns={nocolumns}
items={items}
pagesspan={pagesspan}
>
{({
getBackButtonProps,
getFwdButtonProps,
getFastFwdButtonProps,
getSelPageButtonProps,
nopages,
inipagearray,
pagesforarray,
currentpage,
noitems,
initialitem,
lastitem,
goBackBdisabled,
goFastBackBdisabled,
goFwdBdisabled,
goFastFwdBdisabled
}) => (
<tbody style={{ alignItems: "center", margin: "auto auto" }}>
{/* {items.slice(initialitem, lastitem).map((item, index) => {
return item;
})} */}
{noitems > 0
? [
<tr key={"pagingrow" + 100} >
<td colSpan={nocolumns} style={{ textAlign: "center" }}>
<button
style={buttonStyles(goBackBdisabled)}
{...getBackButtonProps()}
disabled={goBackBdisabled}
>
{"<"}
</button>
{Array.from(
{ length: pagesforarray },
(v, i) => i + inipagearray
).map(page => {
return (
<button
key={page}
{...getSelPageButtonProps({ page: page })}
disabled={currentpage == page}
style={{ margin: "0.5em", backgroundColor: "transparent", border: "none" }}
onClick={e => page != currentpage ? pageNumClick(page, e, currentpage) : {}}
>
{page}
</button>
);
})}
<button
style={buttonStyles(goFwdBdisabled)}
{...getFwdButtonProps()}
disabled={goFwdBdisabled}
>
{">"}
</button>
</td>
</tr>
]
: null}
</tbody>
)}
</ReactNextPaging>
Page redirection handle code :
const pageNumClick = (page, e, currentpage) => {
let el = document.getElementsByClassName(`.clickable-page-${page}`)
console.log(el)
e.target.style.backgroundColor = "#353E5A";
currentpage = page;
console.log(page, "clicked page number", e.target, currentpage)
//Redirects to the URL with clicked page number
router.push({
pathname: router.pathname,
query: { show: showname, page: page }
})
refreshData(); // Try to refresh props once the URL is changed
}
const refreshData = () => {
router.replace(router.asPath);
console.log('refreshed')
}
Attempts to resolve:
Added refreshData method to invoke ServerSideProps upon URL change based on this.
Tried changing getServerSideProps to getInitialProps - with no luck
Any help or links would be appreciated, been stuck with the task since 3 days
Issue is caused by the refreshdata function, router.asPath will have your current url.
Below code is working fine for me.
function ProductDetail({ products, page,limit }) {
const router = useRouter();
const pageNumClick = (page, limit) => {
router.push({
pathname: router.pathname,
query: { limit: limit, page: page },
});
};
return (
<div>
<div onClick={() => pageNumClick(parseInt(page) + 1, limit)}>Next page</div>
<div onClick={() => pageNumClick(parseInt(page) - 1, limit)}>
Previous page
</div>
{products ? JSON.stringify(products) : <></>}
</div>
);
}
export async function getServerSideProps({ params, query, ...props }) {
const products = await getProducts(query.limit, query.page);
return {
props: {
products: products ? products : {},
page: query.page,
limit: query.limit,
},
};
}
I got this error:
vue.runtime.esm.js?2b0e:619 [Vue warn]: Avoid mutating a prop directly
since the value will be overwritten whenever the parent component
re-renders. Instead, use a data or computed property based on the
prop's value. Prop being mutated: "isOpen"
Here is my code
My child component:
<template>
<v-dialog v-model="isOpen" max-width="500px">
<v-card>
<v-card-title>Remove</v-card-title>
<v-card-text>Are you sure to delete?</v-card-text>
<v-card-actions>
<v-btn color="primary" text #click="$emit('closedialog')">Close</v-btn>
<!-- <v-btn color="primary" text #click="deleteItem">Delete</v-btn> -->
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
// name: 'confirmDelete',
props: {
isOpen: Boolean
// selected: Object
}
};
</script>
Parent Component:
<template>
<div class="container">
<div id="app">
<v-app id="inspire">
<v-data-table
:headers="headers"
:items="contracts"
sort-by="createdAt"
class="elevation-1"
>
<template v-slot:top>
<v-toolbar flat color="white">
<v-toolbar-title>CONTRACTS</v-toolbar-title>
<v-divider class="mx-4" inset vertical></v-divider>
<v-spacer></v-spacer>
<v-dialog v-model="dialog" max-width="500px">
<template v-slot:activator="{ on, attrs }">
<v-btn
color="primary"
dark
class="mb-2"
v-bind="attrs"
v-on="on"
>New Contract</v-btn>
</template>
<v-card>
<v-card-title>
<span class="headline">{{ formTitle }}</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12" sm="6" md="4">
<v-text-field
label="Start Contract"
name="name"
prepend-icon="person"
placeholder="YYYY-MM-DD"
type="text"
required
v-model="selectedItem.startDate"
:rules="nameErrors"
#input="$v.selectedItem.startDate.$touch()"
#blur="$v.selectedItem.startDate.$touch()"
#keyup="clearServerErrors('name')"
/>
</v-col>
<v-col cols="12" sm="6">
<v-select
v-model="selectedItem.duration"
:items="[1, 2, 3, 6, 12]"
label="Duration Contract."
required
/>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-select
v-model="selectedItem.leave"
:items="[20, 26]"
label="Days off"
/>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text #click="close">Cancel</v-btn>
<v-btn
color="blue darken-1"
text
#click="onSave"
:disabled="!isValid"
>Save</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
</template>
<template v-slot:item.actions="{ item }">
<v-icon small class="mr-2" #click="editItem(item)">mdi-pencil</v-icon>
<v-icon small #click="showDeleteDialog(item)">mdi-delete</v-icon>
<v-icon middle #click="goToRouteLeaves(item)">play_arrow</v-icon>
</template>
</v-data-table>
<CreateOrEditContract :is-open="isDialogDeleteVisible" #closedialog="close()" />
<!-- <v-dialog v-model="isDialogDeleteVisible" max-width="500px">
<v-card>
<v-card-title>Remove</v-card-title>
<v-card-text>Are you sure to delete?</v-card-text>
<v-card-actions>
<v-btn color="primary" text #click="isDialogDeleteVisible = false">Close</v-btn>
<v-btn color="primary" text #click="deleteItem">Delete</v-btn>
</v-card-actions>
</v-card>
</v-dialog>-->
</v-app>
</div>
</div>
</template>
<script>
import ContractService from '../services/ContractService';
import UserContractsService from '../services/UserContractsService';
import { validationMixin } from 'vuelidate';
import { required } from 'vuelidate/lib/validators';
import CreateOrEditContract from './CreateOrEditContract';
var moment = require('moment');
export default {
name: 'Admin',
components: {
CreateOrEditContract
},
mixins: [validationMixin],
validations: {
selectedItem: {
startDate: {
required,
isStartDate(value) {
return this.isStartDate(value);
}
}
}
},
data() {
return {
selectedItem: {
startDate: ''
},
serverErrors: {
startDate: ''
},
errorMessage: '',
error: null,
validationError: false,
contracts: [],
dialog: false,
isDialogDeleteVisible: false,
headers: [
{ text: 'Start', value: 'startDate' },
{ text: 'Duration', value: 'duration' },
{ text: 'Leave', value: 'leave' },
{ text: 'Actions', value: 'actions', sortable: false }
],
defaultItem: {
startDate: '',
duration: '',
leave: ''
}
};
},
created() {
this.selectedItem = { ...this.defaultItem };
},
async mounted() {
try {
const { userId } = this.$route.params;
const { data } = await UserContractsService.index(userId);
this.contracts = data;
} catch (error) {
this.errorMessage =
(error.response && error.response.data ? error.response.data : null) ||
error.message ||
error.toString();
}
},
computed: {
formTitle() {
return this.selectedItem.id ? 'Edit Contract' : 'New Contract';
},
nameErrors() {
const errors = [];
if (!this.$v.selectedItem.startDate.$dirty) return errors;
!this.$v.selectedItem.startDate.required && errors.push('Date is required');
!this.$v.selectedItem.startDate.isStartDate && errors.push('Enter valid date');
return errors;
},
isValid() {
return !this.$v.$invalid;
}
},
watch: {
dialog(val) {
val || this.close();
}
},
methods: {
editItem(item) {
this.selectedItem = { ...item };
this.dialog = true;
},
async deleteItem() {
const index = this.contracts.findIndex((contract) => contract.id === this.selectedItemlete.id);
this.contracts.splice(index, 1);
this.isDialogDeleteVisible = false;
await ContractService.delete(this.selectedItemlete.id);
this.selectedItemlete = { ...this.defaultItem };
},
showDeleteDialog(item) {
this.selectedItemlete = item;
this.isDialogDeleteVisible = true; //!this.isDialogDeleteVisible;
// this.$emit("clicked", !this.isDialogDeleteVisible)
},
close() {
this.isDialogDeleteVisible = false;
this.dialog = false;
this.selectedItem = { ...this.defaultItem };
},
async onSave() {
if (this.selectedItem.id) {
const index = this.contracts.findIndex((contract) => contract.id === this.selectedItem.id);
await ContractService.save(this.selectedItem);
this.$set(this.contracts, index, this.selectedItem);
} else {
this.selectedItem.userId = this.$route.params.userId;
const { data } = await ContractService.save(this.selectedItem);
this.contracts.push(data);
}
this.close();
},
goToRouteLeaves(item) {
this.$router.push(`/leaves/${item.id}`);
},
clearServerErrors(type) {
this.serverErrors[type] = [];
},
isStartDate(value) {
return moment(value, 'YYYY-MM-DD', true).isValid();
}
}
};
</script>
<style scoped>
v-btn {
position: absolute;
}
</style>
v-model="isOpen" your child component is trying to change the props isOpen.
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders.
make change like below:
Parent:
<CreateOrEditContract :is-open.sync="isDialogDeleteVisible" #closedialog="close()" />
Child:
computed: {
open: {
// getter
get: function () {
return this.isOpen
},
// setter
set: function (newValue) {
this.$emit('update:isOpen', newValue)
}
}
}
<v-dialog v-model="open" max-width="500px">
It's because props work from top to bottom pattern. isOpen passed by parent to child, now it's like data flowing from top to bottom. If your child tries to mutate that data, how will parents get informed about that change? The parent will never get informed this way that's why it's a warning to not change the value of prop passed in child. You need to find a way to communicate to parents and parents will update that prop, this way data flow will not break.
Here v-model is two-way binding which means it will set the value of the property which is isOpen prop.
<template>
<v-dialog v-model="isOpen" max-width="500px">
<v-card>
<v-card-title>Remove</v-card-title>
<v-card-text>Are you sure to delete?</v-card-text>
<v-card-actions>
<v-btn color="primary" text #click="$emit('closedialog')">Close</v-btn>
<!-- <v-btn color="primary" text #click="deleteItem">Delete</v-btn> -->
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
// name: 'confirmDelete',
props: {
isOpen: Boolean
// selected: Object
}
};
</script>
Hint: You can emit event to inform parent about the change and make parent change the value of isOpen. Try computed getter/setter to achieve this
I have developed a blog with Gatsby JS and I managed to add categories to each markdown file so that I can create pages by querying a specific category and list all the posts related to that category.
Now, I'm trying to add pagination to avoid an infinite list of posts inside each category page.
I have been following the official guide here: https://www.gatsbyjs.org/docs/adding-pagination/
And this is the code I came up with:
gatsby-node.js
const path = require('path')
const _ = require("lodash")
const { createFilePath } = require("gatsby-source-filesystem")
exports.createPages = ({actions, graphql}) => {
const {createPage} = actions
const articleTemplate = path.resolve(`src/templates/article.js`)
const categoryTemplate = path.resolve("src/templates/category.js")
return new Promise((resolve, reject) => {
resolve(
graphql(
`
{
allMarkdownRemark(
sort: { order: DESC, fields: [frontmatter___date] }
limit: 2000
) {
edges {
node {
html
id
frontmatter {
path
title
categories
}
}
}
}
}
`).then(result => {
if (result.errors) {
reject(result.errors)
}
const articles = result.data.allMarkdownRemark.edges
const articlesPerPage = 6
const numPages = Math.ceil(articles.length / articlesPerPage)
//Creating a page for each article
articles.forEach(({ node }) => {
createPage({
path: node.frontmatter.path,
component: articleTemplate,
//context: {}, // additional data can be passed via context
})
})
// Categories pages:
let categories = []
// Iterate through each article, putting all found categories into `categories`
_.each(articles, edge => {
if (_.get(edge, "node.frontmatter.categories")) {
categories = categories.concat(edge.node.frontmatter.categories)
}
})
Array.from({ length: numPages }).forEach((category, _, i) => {
createPage({
path: i === 0 ? `/${_.kebabCase(category)}/` : `/${_.kebabCase(category)}/${i + 1}`,
component: categoryTemplate,
context: {
limit: articlesPerPage,
skip: i * articlesPerPage,
category,
},
})
})
})
)
})
/templates/categories.js
import React from "react"
import PropTypes from "prop-types"
import Layout from '../layouts/layout'
import ArticleCard from '../components/articles/articlecard'
// Components
import { Link, graphql } from "gatsby"
const _ = require("lodash")
const Categories = ({ pageContext, data }) => {
const { category } = pageContext
const { edges } = data.allMarkdownRemark
return (
<Layout>
<section class="hero is-info is-medium has-text-centered">
<div class="hero-body">
<div class="container">
<h1 class="title is-top">
{category}
</h1>
</div>
</div>
</section>
<div class="section">
<div class="container">
<div class="columns is-multiline">
{edges.map(({ node }) => {
const { path, title, date } = node.frontmatter
return (
<div class="column is-half">
<div class="card">
<div class="card-header">
<p class="card-header-content">{date}</p>
</div>
<div class="card-content">
<Link to={_.kebabCase(category)}><span class="tag is-success has-padding">{category}</span></Link>
<Link to={path}>
<h2 class="title is-4">{title}</h2>
</Link>
</div>
<div class="card-footer">
<div class="card-footer-item"><Link to={path}><div class="button is-success is-inverted is-fullwidth">Read</div></Link></div>
<div class="card-footer-item"><Link to={path}><div class="button is-info is-inverted is-fullwidth">Share on Linkedin</div></Link></div>
</div>
</div>
</div>
)
})}
</div>
</div>
</div>
</Layout>
)
}
Categories.propTypes = {
pageContext: PropTypes.shape({
category: PropTypes.string.isRequired,
}),
data: PropTypes.shape({
allMarkdownRemark: PropTypes.shape({
totalCount: PropTypes.number.isRequired,
edges: PropTypes.arrayOf(
PropTypes.shape({
node: PropTypes.shape({
frontmatter: PropTypes.shape({
path: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
}),
}),
}).isRequired
),
}),
}),
}
export default Categories
export const pageQuery = graphql`
query($skip: Int!, $limit: Int!, $category: String) {
allMarkdownRemark(
sort: { fields: [frontmatter___date], order: DESC }
filter: { frontmatter: { categories: { in: [$category] } } }
limit: $limit
skip: $skip
) {
totalCount
edges {
node {
frontmatter {
title
path
date(formatString: "MMMM DD, YYYY")
}
}
}
}
}
`
This does not work and it is now throwing the error: error gatsby-node.js returned an error, TypeError: _.kebabCase is not a function
However kebabCase was used smoothly before modifying the query to add pagination, so I don't think the problem is actually there.
Does anyone have any clue?
Thank you!
You are declaring the variable "underscore" twice:
1- from the lodash library
2- from the forEach function:
Array.from({ length: numPages }).forEach((category, _, i)
just change the the second variable to another arbitrary name like this:
Array.from({ length: numPages }).forEach((category, otherName, i)
Am trying to make a graph with data received from an API and put this on a graph (https://bl.ocks.org/mbostock/4062045) - Force-Directed Graph
However I am unsure on how this is done on VueJs or if there is a simpler tool to do this?
D3 Force-Directed Graph seems a bit complicated, maybe there is a library already that does this out of the box?
The mentioned vue-d3 package from the comment is just adding D3 to the Vue prototype so it is accessible with this.$d3.
I've tested that package but it wasn't working with my D3 version. Looks like a casing issue (D3 instead of d3). So I've added the prototype manually.
I don't know if there is an easier library for creating a force graph but please have a look at the demo below or this fiddle.
I've modified the example from your link to create a force directed graph. The demo is working but as you've mentioned it's pretty complicated.
Also binding from SVG to Vue.js model could be improved. But I couldn't find a better way to do it.
For example adding a new node on click is not working with just adding a new node to the array but this should be the goal for a Vue.js component. The SVG graph should automatically update once the data changes.
At the moment, nodes and links in Vue.js are not used in the component because I don't know how to add the updating of the graph.
If you figured out how to add the updating with the model data, please let me know. Refreshing the whole chart is pretty easy by deleting the SVG and re-create it. (see reload button)
// https://unpkg.com/vue-d3#0.1.0 --> only adds d3 to Vue.prototype but it wasn't working as expected (d3 is lower case)
Vue.prototype.$d3 = d3;
const URL = 'https://demo5147591.mockable.io/miserables'; // data copied from below link because of jsonp support
//'https://bl.ocks.org/mbostock/raw/4062045/5916d145c8c048a6e3086915a6be464467391c62/miserables.json';
//console.log(window.d3);
const d3ForceGraph = {
template: `
<div>
{{mousePosition}}
<button #click="reload">reload</button>
<svg width="600" height="600"
#mousemove="onMouseMove($event)"></svg>
</div>
`,
data() {
return {
nodes: [],
links: [],
simulation: undefined,
mousePosition: {
x: 0,
y: 0
}
}
},
mounted() {
this.loadData(); // initially load json
},
methods: {
// load data
loadData() {
this.$svg = $(this.$el).find('svg');
let svg = this.$d3.select(this.$svg.get(0)), //this.$d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
//console.log($(this.$el).find('svg').get(0));
this.simulation = this.$d3.forceSimulation()
.force("link", this.$d3.forceLink().id(function(d) {
return d.id;
}))
.force("charge", this.$d3.forceManyBody())
.force("center", this.$d3.forceCenter(width / 2, height / 2));
let color = this.$d3.scaleOrdinal(this.$d3.schemeCategory20);
$.getJSON(URL, (graph) => {
//d3.json("miserables.json", function(error, graph) { // already loaded
//if (error) throw error; // needs to be implemented differently
let nodes = graph.nodes;
let links = graph.links;
let link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(links) //graph.links)
.enter().append("line")
.attr("stroke-width", function(d) {
return Math.sqrt(d.value);
});
let node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes) //graph.nodes)
.enter().append("circle")
.attr("r", 5)
.attr("fill", function(d) {
return color(d.group);
})
.call(this.$d3.drag()
.on("start", this.dragstarted)
.on("drag", this.dragged)
.on("end", this.dragended));
node.append("title")
.text(function(d) {
return d.id;
});
this.simulation
.nodes(graph.nodes)
.on("tick", ticked);
this.simulation.force("link")
.links(links); //graph.links);
function ticked() {
link
.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
node
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
}
})
},
reload() {
//console.log('reloading...');
this.$svg.empty(); // clear svg --> easiest way to re-create the force graph.
this.loadData();
},
// mouse events
onMouseMove(evt) {
//console.log(evt, this)
this.mousePosition = {
x: evt.clientX,
y: evt.clientY
}
},
// drag event handlers
dragstarted(d) {
if (!this.$d3.event.active) this.simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
},
dragged(d) {
d.fx = this.$d3.event.x;
d.fy = this.$d3.event.y;
},
dragended(d) {
if (!this.$d3.event.active) this.simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
}
};
new Vue({
el: '#app',
data() {
return {}
},
components: {
d3ForceGraph
}
});
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.8.0/d3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="app">
<d3-force-graph></d3-force-graph>
</div>
I answered another question on vue + d3 by providing an example of a d3 force graph with vue.js.
d3.js is now split in small modules and specific computations are isolated in small components like d3-force. SVG can be drawn in a component template like any other HTML structure.
You can use vue-d3-network
npm install vue-d3-network
see this fiddle
html:
```
<head>
<script type="text/javascript" src="https://unpkg.com/vue">
</script>
<link rel="stylesheet" type="text/css" href="https://rawgit.com/emiliorizzo/vue-d3-network/master/dist/vue-d3-network.css">
<script type="text/javascript" src="https://rawgit.com/emiliorizzo/vue-d3-network/master/dist/vue-d3-network.umd.js"></script>
</head>
<body>
<div id="app">
<d3-network :net-nodes="nodes" :net-links="links" :options="options">
</d3-network>
</div>
</body>
```
javascript:
```
var D3Network = window['vue-d3-network']
new Vue({
el: '#app',
components: {
D3Network
},
data () {
return {
nodes: [
{ id: 1, name: 'my node 1' },
{ id: 2, name: 'my node 2' },
{ id: 3, _color:'orange' },
{ id: 4 },
{ id: 5 },
{ id: 6 },
{ id: 7 },
{ id: 8 },
{ id: 9 }
],
links: [
{ sid: 1, tid: 2, _color:'red' },
{ sid: 2, tid: 8, _color:'f0f' },
{ sid: 3, tid: 4,_color:'rebeccapurple' },
{ sid: 4, tid: 5 },
{ sid: 5, tid: 6 },
{ sid: 7, tid: 8 },
{ sid: 5, tid: 8 },
{ sid: 3, tid: 8 },
{ sid: 7, tid: 9 }
],
options:
{
force: 3000,
nodeSize: 20,
nodeLabels: true,
linkWidth:5
}
}
},
})
```
I have parent and child component. I want the parent to render multiple child components with properties specified in an object. I cannot seem to make the loop in the render function work.
var Inputs = React.createClass({
propTypes: {
type: React.PropTypes.string,
xmltag: React.PropTypes.string,
class: React.PropTypes.string
},
getDefaultProps: function () {
return {
type: ' text'
};
},
render: function() {
return (
<div className={'form-element col-xs-6 col-sm-6 ' + this.props.class}>
<label className="col-xs-12">{this.props.text}</label>
<input className="col-xs-12" type={this.props.type} xmltag={this.props.xmltag}></input>
</div>
);
},
});
//OBJECT that needs to be rendered
var formTags = {
id: ["ID", "List ID", "text"],
title: ["TITLE", "List Title", "text"],
said: ["SAID", "SAID", "number"]
};
var InputList = React.createClass({
//PROBLEM STARTS HERE
render: function() {
for (var key in formTags) {
return (
//Not using everything from formTags
<Inputs type="number" text={key}>
);
};
},
//PROBLEM ENDS HERE
});
ReactDOM.render(<InputList />, document.getElementById('mainForm'));
React component must have only one root element, now you are trying render several elements, add one root element, like in example (you can use any elements <div> <p> etc.)
var InputList = React.createClass({
render: function() {
var inputs = Object.keys(this.props.tags).map(function (key) {
return <Inputs key={ key } type="number" text={ key } />;
});
return <div>
{ inputs }
</div>;
}
});
Example