I need to be able take a list of objects in liquid and display them on the page in a specific format.
If I have an array of objects (pages), I need to be able to print them in the following way:
list category names (page.category.name)
list each subcategory name with a list of pages under each subcategory name (page.subcategory.name and page.title)
Typically in ruby I would simply group the pages but I can't do that in liquid. The other thing I tried was to capture unique lists of categories and subcategories for pages but I couldn't find a way to get a unique list of items from an array. Any suggest help would be great.
I'm a little late to answer your question, but maybe this will help someone else struggling with the same problem. It's a bit hacky, as Liquid logic tends to be, but it works (at least for me, on Shopify).
Assuming you have a 'pages' array that looks like this:
pages = [
{ name: 'Page 1', category: { name: 'pants' } },
{ name: 'Page 2', category: { name: 'pants' } },
{ name: 'Page 3', category: { name: 'shoes' } },
{ name: 'Page 4', category: { name: 'shirts' } },
{ name: 'Page 5', category: { name: 'shoes' } }
]
This code will return only unique category names:
{% assign delimiter = "," %}
{% assign names_str = "" %}
{% assign names = pages | map: 'category' | map: 'name' %}
{% for name in names %}
{% assign names_arr = names_str | split: delimiter %}
{% unless names_arr contains name %}
{% assign names_str = names_str | append: delimiter | append: name %}
{% endunless %}
{% endfor %}
{% assign names_uniq = names_str | remove_first: delimiter | split: delimiter %}
Result:
names_uniq => [ 'pants', 'shoes', 'shirts' ]
Related
I use the awesome paginate plugin for Gatsby to do pagination on archive pages on the site. The plugin connects to the gatsby-node file.js is called there with the help of such code
paginate({
createPage,
items: allMdx.edges,
itemsPerPage: 1,
pathPrefix: '/work',
component: path.resolve('src/templates/work.js')
});
Pagination works great on blog and works pages. I created these pages using the Gatsby Route API, without using the createPage function in gatsby-node. Pagination works great on blog and works pages. I created these pages using the Gatsby Route API, without using the createPage function in gatsby-node.js . But on the pages of tags and categories, I can't enable pagination. These pages were created using createPage. Below I present the code of my gatsby-node.js
const _ = require("lodash")
//const readingTime = require("reading-time")
const { paginate } = require('gatsby-awesome-pagination')
const path = require("path")
const { transliterate } = require('./src/functions/transletter')
const { createFilePath } = require('gatsby-source-filesystem')
/* Create category page */
function dedupeCategories(allMdx) {
const uniqueCategories = new Set()
// Iterate over all articles
allMdx.edges.forEach(({ node }) => {
// Iterate over each category in an article
node.frontmatter.categories.forEach(category => {
uniqueCategories.add(category)
})
})
// Create new array with duplicates removed
return Array.from(uniqueCategories)
}
/* Create tag page */
function dedupeTags(allMdx) {
const uniqueTags = new Set()
// Iterate over all articles
allMdx.edges.forEach(({ node }) => {
// Iterate over each category in an article
node.frontmatter.tags.forEach(tag => {
uniqueTags.add(tag)
})
})
// Create new array with duplicates removed
return Array.from(uniqueTags)
}
exports.createPages = async ({ graphql, actions, reporter }) => {
const { createPage } = actions
// Query markdown files including data from frontmatter
const {
data: { allMdx },
} = await graphql(`
query {
allMdx (filter: {frontmatter: {type: {in: "blog"}}}){
edges {
node {
id
frontmatter {
categories
tags
slug
}
fields {
slug
}
}
}
}
}
`)
// Create array of every category without duplicates
const dedupedCategories = dedupeCategories(allMdx)
// Iterate over categories and create page for each
dedupedCategories.forEach(category => {
reporter.info(`Creating page: blog/category/${category}`)
createPage({
path: `blog/category/${_.kebabCase(transliterate(category))}`,
component: require.resolve("./src/templates/categories.js"),
// Create props for our CategoryList.js component
context: {
category,
// Create an array of ids of articles in this category
ids: allMdx.edges
.filter(({ node }) => {
return node.frontmatter.categories.includes(category)
})
.map(({node}) => node.id),
},
})
})
// Create array of every category without duplicates
const dedupedTags = dedupeTags(allMdx)
// Iterate over categories and create page for each
dedupedTags.forEach(tag => {
reporter.info(`Creating page: blog/tag/${tag}`)
createPage({
path: `blog/tag/${_.kebabCase(transliterate(tag))}`,
component: require.resolve("./src/templates/tags.js"),
// Create props for our CategoryList.js component
context: {
tag,
// Create an array of ids of articles in this category
ids: allMdx.edges
.filter(({ node }) => {
return node.frontmatter.tags.includes(tag)
})
.map(({node}) => node.id),
},
})
})
/* It's creating a new page for each post. */
paginate({
createPage,
items: allMdx.edges,
itemsPerPage: 1,
pathPrefix: '/blog',
component: path.resolve('src/templates/blog.js')
});
/* It's creating a new page for each post. */
paginate({
createPage,
items: allMdx.edges,
itemsPerPage: 1,
pathPrefix: '/work',
component: path.resolve('src/templates/work.js')
});
/* It's creating a new page for each post. */
paginate({
createPage,
items: allMdx.edges,
itemsPerPage: 1,
pathPrefix: '/blog/category',
component: path.resolve('src/templates/categories.js')
});
}
/* Creating a slug for each post. */
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions;
if (node.internal.type === `Mdx`) {
const value = createFilePath({ node, getNode });
createNodeField({
name: 'slug',
node,
value,
});
}
};
/* It's creating a new field in the GraphQL schema called `relatedPosts` that returns an array of Mdx
nodes. */
exports.createResolvers = ({ createResolvers }) => {
const resolvers = {
Mdx: {
relatedPosts: {
type: ['Mdx'],
resolve: (source, args, context, info) => {
return context.nodeModel.runQuery({
query: {
filter: {
id: {
ne: source.id,
},
frontmatter: {
// type: {
// ne: source.frontmatter.type === `work`,
// },
tags: {
in: source.frontmatter.tags,
},
},
},
},
type: 'Mdx',
})
},
},
},
}
createResolvers(resolvers)
}
And this is the template code of my category src/templates/category.js
import React from "react"
import { Link, graphql } from "gatsby"
import Layout from '../components/layout'
import Seo from '../components/seo'
import Pager from '../components/pagination'
const CategoryList = ({ pageContext: { category }, data: { allMdx }, pageContext }) =>
(
<Layout pageTitle={category}>
{
allMdx.edges.map(({ node }) => {
return (
<article key={node.id}>
<h2>
<Link to={`/blog${node.fields.slug}`}>
{node.frontmatter.title}
</Link>
</h2>
<p>Posted: {node.frontmatter.date}</p>
<p>{node.excerpt}</p>
</article>
)
})
}
<Pager pageContext={pageContext} />
</Layout>
)
export const query = graphql`
query CategoryListQuery($ids: [String]!, $limit: Int!, $skip: Int!) {
allMdx (filter: { id: { in: $ids }, frontmatter: {type: {in: "blog"}}}, limit: $limit,
skip: $skip) {
edges {
node {
frontmatter {
title
date(formatString: "MMMM DD, YYYY")
}
fields {
slug
}
id
excerpt
}
}
}
}
`
export const Head = ({ pageContext }) => (
<Seo
title={pageContext.category}
description={`Статьи из категории ${pageContext.category}`}
/>
)
export default CategoryList
The Pager component looks like this
import React from 'react';
import { Link } from 'gatsby';
const Pager = ({ pageContext }) => {
const { previousPagePath, nextPagePath } = pageContext;
return (
<nav style={{ display: 'flex', justifyContent: 'space-between' }}>
<div>
{previousPagePath && (
<Link to={previousPagePath}>
<button>← Newer Posts</button>
</Link>
)}
</div>
<div style={{ justifySelf: 'flex-end' }}>
{nextPagePath && (
<Link to={nextPagePath}>
<button>Older Posts →</button>
</Link>
)}
</div>
</nav>
);
};
export default Pager;
This is errors
ERROR #85920 GRAPHQL
There was an error in your GraphQL query:
Variable "$limit" of required type "Int!" was not provided.
> 1 | query CategoryListQuery($ids: [String]!, $limit: Int!, $skip: Int!) {
| ^
2 | allMdx(
3 | filter: {id: {in: $ids}, frontmatter: {type: {in: "blog"}}}
4 | limit: $limit
5 | skip: $skip
6 | ) {
7 | edges {
8 | node {
9 | frontmatter {
10 | title
11 | date(formatString: "MMMM DD, YYYY")
File path: C:/gatsby/schtml/src/templates/categories.js
Url path: /blog/category/gatsby-js
Plugin: none
Suggestion 1:
If you're not using a page query but a useStaticQuery / StaticQuery you see this error because they currently don't support
variables. To learn more about the limitations of useStaticQuery / StaticQuery, please visit these docs:
https://www.gatsbyjs.com/docs/how-to/querying-data/use-static-query/
https://www.gatsbyjs.com/docs/how-to/querying-data/static-query/
Suggestion 2:
You might have a typo in the variable name "$limit" or you didn't provide the variable via context to this page query. Have a look
at the docs to learn how to add data to context:
https://www.gatsbyjs.com/docs/how-to/querying-data/page-query#how-to-add-query-variables-to-a-page-query
ERROR #85920 GRAPHQL
There was an error in your GraphQL query:
Variable "$skip" of required type "Int!" was not provided.
> 1 | query CategoryListQuery($ids: [String]!, $limit: Int!, $skip: Int!) {
| ^
2 | allMdx(
3 | filter: {id: {in: $ids}, frontmatter: {type: {in: "blog"}}}
4 | limit: $limit
5 | skip: $skip
6 | ) {
7 | edges {
8 | node {
9 | frontmatter {
10 | title
11 | date(formatString: "MMMM DD, YYYY")
File path: C:/gatsby/schtml/src/templates/categories.js
Url path: /blog/category/gatsby-js
Plugin: none
Suggestion 1:
If you're not using a page query but a useStaticQuery / StaticQuery you see this error because they currently don't support
variables. To learn more about the limitations of useStaticQuery / StaticQuery, please visit these docs:
https://www.gatsbyjs.com/docs/how-to/querying-data/use-static-query/
https://www.gatsbyjs.com/docs/how-to/querying-data/static-query/
Suggestion 2:
You might have a typo in the variable name "$skip" or you didn't provide the variable via context to this page query. Have a look at the docs to learn how to add data to context:
https://www.gatsbyjs.com/docs/how-to/querying-data/page-query#how-to-add-query-variables-to-a-page-query
ERROR #85920 GRAPHQL
There was an error in your GraphQL query:
Variable "$limit" of required type "Int!" was not provided.
> 1 | query CategoryListQuery($ids: [String]!, $limit: Int!, $skip: Int!) {
| ^
2 | allMdx(
3 | filter: {id: {in: $ids}, frontmatter: {type: {in: "blog"}}}
4 | limit: $limit
5 | skip: $skip
6 | ) {
7 | edges {
8 | node {
9 | frontmatter {
10 | title
11 | date(formatString: "MMMM DD, YYYY")
File path: C:/gatsby/schtml/src/templates/categories.js
Url path: /blog/category/gatsby-develop
Plugin: none
Suggestion 1:
If you're not using a page query but a useStaticQuery / StaticQuery you see this error because they currently don't support
variables. To learn more about the limitations of useStaticQuery / StaticQuery, please visit these docs:
https://www.gatsbyjs.com/docs/how-to/querying-data/use-static-query/
https://www.gatsbyjs.com/docs/how-to/querying-data/static-query/
Suggestion 2:
You might have a typo in the variable name "$limit" or you didn't provide the variable via context to this page query. Have a look
at the docs to learn how to add data to context:
https://www.gatsbyjs.com/docs/how-to/querying-data/page-query#how-to-add-query-variables-to-a-page-query
ERROR #85920 GRAPHQL
There was an error in your GraphQL query:
Variable "$skip" of required type "Int!" was not provided.
> 1 | query CategoryListQuery($ids: [String]!, $limit: Int!, $skip: Int!) {
| ^
2 | allMdx(
3 | filter: {id: {in: $ids}, frontmatter: {type: {in: "blog"}}}
4 | limit: $limit
5 | skip: $skip
6 | ) {
7 | edges {
8 | node {
9 | frontmatter {
10 | title
11 | date(formatString: "MMMM DD, YYYY")
File path: C:/gatsby/schtml/src/templates/categories.js
Url path: /blog/category/gatsby-develop
Plugin: none
Suggestion 1:
If you're not using a page query but a useStaticQuery / StaticQuery you see this error because they currently don't support
variables. To learn more about the limitations of useStaticQuery / StaticQuery, please visit these docs:
https://www.gatsbyjs.com/docs/how-to/querying-data/use-static-query/
https://www.gatsbyjs.com/docs/how-to/querying-data/static-query/
Suggestion 2:
You might have a typo in the variable name "$skip" or you didn't provide the variable via context to this page query. Have a look at the docs to learn how to add data to context:
https://www.gatsbyjs.com/docs/how-to/querying-data/page-query#how-to-add-query-variables-to-a-page-query
success Writing page-data.json files to public directory - 0.044s - 2/2 45.97/s
success run page queries - 0.422s - 3/3 7.12/s
success Writing page-data.json files to public directory - 0.059s - 3/3 50.83/s
Is there a possibility to plot the errorbars in highcharts behind the actual lines/datapoints?
I am usually plotting the data and the errorbars with a for loop, therefore it would be convenient to set an attribute for the errorbars.
series: [
{% for key in dataset %}
{
name: '{{key}}',
data : {{ dataset[key].data}}
},
{
name: ' error',
type: 'errorbar',
data : {{ dataset[key].error}}
},
{% endfor %}
],
This can be controlled by the zIndex, like this:
series: [
{% for key in dataset %}
{
name: '{{key}}',
data : {{ dataset[key].data}},
zIndex: 2
},
{
name: ' error',
type: 'errorbar',
data : {{ dataset[key].error}},
zIndex: 1
},
{% endfor %}
]
API on series.zIndex: https://api.highcharts.com/highcharts/series.errorbar.zIndex
Define the visual z index of the series.
Defaults to undefined.
Working example: http://jsfiddle.net/ewolden/vrdmdzmz/1/
Cypher newbie here.
I made this graph of tagged Media using the code below.
CREATE
(funny:Tag { name: 'Funny' }),
(sad:Tag { name: 'Sad' }),
(movie:Tag { name: 'Movie' }),
(tv:Tag { name: 'TV Show' }),
(hangover:Media { name: 'The Hangover' }),
(koth:Media { name: 'King of the Hill' }),
(simpsons:Media { name: 'The Simpsons' }),
(twm:Media { name: 'Tuesdays with Morrie' }),
(mm:Media { name: 'Mary & Max' }),
(funny)-[:DESCRIBES]->(hangover),
(funny)-[:DESCRIBES]->(koth),
(funny)-[:DESCRIBES]->(simpsons),
(sad)-[:DESCRIBES]->(twm),
(sad)-[:DESCRIBES]->(mm),
(movie)-[:DESCRIBES]->(hangover),
(movie)-[:DESCRIBES]->(twm),
(movie)-[:DESCRIBES]->(mm),
(tv)-[:DESCRIBES]->(koth),
(tv)-[:DESCRIBES]->(simpsons)
What I want to do is group Tags together into Contexts, such that one Context node has the same meaning as multiple Tags.
MATCH
(tf:Tag { name: 'Funny' }),
(tr:Tag { name: 'Sad' }),
(tm:Tag { name: 'Movie' })
(tt:Tag { name: 'TV Show' })
CREATE
(fm:Context { name: 'Funny Movies' }),
(ft:Context { name: 'Funny TV' }),
(s:Context { name: 'Sad Movies' }),
(fm)-[:INCLUDES]->(tf),
(fm)-[:INCLUDES]->(tm),
(ft)-[:INCLUDES]->(tf),
(ft)-[:INCLUDES]->(tt),
(s)-[:INCLUDES]->(tm),
(s)-[:INCLUDES]->(tr)
So now we have this thing.
I want to take a Context node and get Media such that ALL Tags in that Context describe each returned Media.
I tried MATCH (c:Context { name: 'Funny Movies' })-[:INCLUDES]->()-[:DESCRIBES]->(m) RETURN m to match media tagged with both Funny and Movies. The expected output was only The Hangover, but I get all Media instead.
It's pretty obvious that I don't understand the kind of query I need to write. What is wrong with my query, and how can I produce the output that I want?
When you start from a context, you can collect the tags and then match movies that are related to ALL the tags. The highlighted words in the previous sentence are the keywords for you as reference in the neo4j documentation :
MATCH (c:Context {name:"Funny Movies"})-[:INCLUDES]->(tag)
WITH collect(tag) AS tags
MATCH (m:Media) WHERE ALL( x IN tags WHERE (x)-[:DESCRIBES]->(m))
RETURN m
You can use the bi-directional pattern:
MATCH (c:Context { name: 'Funny Movies' })-[:INCLUDES]->()-[:DESCRIBES]
->(m)<-
[:DESCRIBES]-()<-[:INCLUDES]-(c)
RETURN m
i am working on angularjs .my LocationList having two properties ID and Name.Id are 1,11,13,14,15.
I wanted name as per exact match ID 10.but problem is i am getting more than object.i mean it's not returning exact match ID value.why it's consider 1,11,13,14 15 as well.below is my code.please let me know how to get exact match value using filter.
Name = $filter('filter')($scope.LocationList, { ID: 10});
<div ng:app="myApp">
<div ng-controller="HelloCntl">
<ul>
<li > <span>{{itemxx[0].label}}</span>
</li>
</ul>
</div>
</div>
angular.module('myApp', [])
.controller('HelloCntl', [ '$scope', '$filter', function($scope, $filter) {
$scope.items = [
{ ID: 8, label: 'item 8' },
{ ID: 9, label: 'item 9' },
{ ID: 13, label: 'item 13' } ,
{ ID: 10, label: 'item 10'},
{ ID: 1, label: 'item 1'},
{ ID: 11, label: 'item 11' },
{ ID: 12, label: 'item 12' }
];
$scope.itemxx = $filter('filter')($scope.items, { ID: 1 });
//console.log(item10);
}]);
her items ID is not ordered.so when i am trying to get value of ID 1 then its returing value of Id 13.
This should work:
angular.module('myApp',[]).filter('ID', function(){
return function(items, value){
return items.filter(function(item) { return item.ID === value });
};
});
Here's a full jsFiddle: http://jsfiddle.net/jochenvanwylick/0suogLpk/
Usage:
<li ng-repeat="item in items | ID: 10">
<span>{{item.label}}</span>
</li>
EDIT
You do not need to write a special filter as it turns out, you can just use out-of-the-box filtering:
<li ng-repeat="item in items | filter: {ID:10}">
<span>{{item.label}}</span>
</li>
EDIT 2
I misunderstood - you're trying to do this in the controller, AND there's a small catch in the AngularJS filter. By default it will treat the object property value as a string and then do a 'contains' like filter operation on it.
If you want exact matching, you'll have to utilize the 3rd argument of the filter function, the comparator and supply an exactmatch comparator:
$filter('filter')($scope.items, { ID: 1 }, function(a,b) { return a === b; });
This will effectively do a 1 === 1 comparison and thus will return true for this case and false in any other. ( http://jsfiddle.net/jochenvanwylick/0suogLpk/7/ )
Your example will return an array of objects matching ID: 1. If you have multiple items in that list, your will get multiple results.
If you know - or don't care - that there will always be excatly one match, you can get your result like this:
var Name = $filter('filter')($scope.LocationList, { ID: 1 }, true)[0];
Notice the indexer [0] on the result of $filter to get the first element in the return list. Also notice the true parameter stating that the match has to be excat (not matching 11, 12, 13, etc)
http://plnkr.co/edit/gY7yeflfpURFfWsfV0Ro?p=preview
If you still don't get the expected result, use console.log($scope.LocationList) and expect the result in the chrome(pref. for me) console window. Alternativly you can write the content like this in the HTML
{{ LocationList | json }}
To inspect the object.
This is how I have created my ember FIXTURE:
window.App = Ember.Application.create();
App.ApplicationAdapter = DS.FixtureAdapter;
App.Category = DS.Model.extend({
name: DS.attr(),
parent_id: DS.attr()
});
App.Category.FIXTURES = [
{
id: 1,
name: 'user1',
email: 'user1#gmail.com',
parent_id: 0
},
{
id: 2,
name: 'user2',
email: 'user2#gmail.com',
parent_id: 1
}
];
Here is a part of my ember view where 'parent-title' is a helper:
{{#each category in controller}}
<tr>
<td>{{category.name}}</td>
<td>{{parent-title category.parent_id}}</td>
<td>Edit/Delete</td>
</tr>
{{/each}}
What I want is that during listing if the parent_id is 0 it should print 'master' else the name of parent category. In my exapmle parent of user2 is id=1 show it should print 'user1'.
Below is the helper I have used:
Ember.Handlebars.helper('parent-title', function(parent_id){
if (parent_id > 0) {
var parent = category.findBy('id', parent_id);
return parent.name;
} else {
return 'master';
}
});
I know if I replace the line App.Category.FIXTURES = [ with var Category = [ I can get it done but I want ot do it with FIXTURES.
I can tell you that accessing data like that is a bad idea. If I were you, I would change parent_id to be a relationship, not an attribute (since that's really what it is). Then you can access the parent's name in templates with category.parent.name. Making it a relationship also gives you a few other luxuries.
But if you want to maintain backward compatibility, try using a computed property.
App.Category = DS.Model.extend({
name: DS.attr(),
parent_id: DS.attr(),
parent: function() {
return DS.PromiseObject.create({
promise: this.get('store').find('category', this.get('parent_id'))
});
}.property('parent_id'),
parent_name: function() {
return this.get('parent.name');
}.property('parent.name')
});
EDIT: If you want to change that into a relationship (which I think it should be), it's fairly simple. Instead of DS.attr, you use DS.belongsTo. For instances.
App.Categor = DS.Model.extend({
name: DS.attr(),
// I dropped the _id part because it's no longer an ID
parent: DS.belongsTo('category', { inverse: null })
});
This tells Ember-Data to interpret the ID you give in the parent field as another category object. So category.get('parent') will return another category object, not a number. But in your case, to make it work, you'll have to convert all of the 0 IDs to null IDs. I wasn't sure if that was possible, which is why I recommended the computed property.
EDIT: To display master in case of a null parent, use the Handlebars if expression.
{{#if parent}}
{{parent.name}}
{{else}}
master
{{/if}}