I've a JSON string:
{
"items": [
{"name": "red" },
{"name": "blue" }
],
"test" : {
"items" :[
{ "name" : "Hello" },
{ "name" : "World" }
]
}
}
How do I print out
<li>Hello</li>
<li>World</li>
I tried with the template below but it doesn't work. It instead prints "Red and blue". I don't have access to change the JSON string, I have to manipulate the template only.
{{#test}}
{{#items}}
<li>{{name}}</li>
{{/items}}
{{/test}}
For some reason, the following code:
<head>
<script src="https://github.com/andyet/ICanHaz.js/raw/master/ICanHaz.js"></script>
<script>
function clicked()
{
ich.addTemplate("user", "{{#test}} {{#items}} <li>{{name}}</li>\n {{/items}} {{/test}}");
document.getElementById("result").innerHTML = ich.user(userData);
}
var userData = {
"items": [
{"name": "red" },
{"name": "blue" }
],
"test" : {
"items" :[
{ "name" : "Hello" },
{ "name" : "World" }
]
}
};
</script>
</head>
<body>
<button onclick="clicked()">CLICK</button>
<ul id="result"><li>Result</li></div>
</body>
gives me exactly:
Hello
World
So, your template should be correct.
Related
I followed the Gatsby tutorial and ended up with a GraphQL like:
query {
allMdx(sort: {fields: frontmatter___date, order: DESC}) {
nodes {
frontmatter {
date(formatString: "MMMM D, YYYY")
title
}
id
slug
}
}
}
This gives me the expected result of:
{
"data": {
"allMdx": {
"nodes": [
{
"frontmatter": {
"date": "January 25, 2022",
"title": "Image Blog"
},
"id": "cc11f4c4-43a5-573a-9ea6-3806f7e2fc64",
"slug": "image-blog/"
},
{
"frontmatter": {
"date": "July 24, 2021",
"title": "Page B"
},
"id": "1fb36e34-d1f4-5ac2-8c18-1030733cfce1",
"slug": "b"
},
{
"frontmatter": {
"date": "July 24, 2021",
"title": "Page C"
},
"id": "2f0ed709-c7ab-5a3f-9846-1340699b4bd1",
"slug": "c"
},
{
"frontmatter": {
"date": "July 24, 2021",
"title": "Sub Page D"
},
"id": "d9cc842d-dc45-508a-8d5d-8dc1f2b0b232",
"slug": "sub/d"
},
{
"frontmatter": {
"date": "July 24, 2021",
"title": "Sub Sub Page E"
},
"id": "2005bd62-37a7-580b-9950-e1b7c3688c0b",
"slug": "sub/subsub/e"
},
{
"frontmatter": {
"date": "July 23, 2021",
"title": "Page A"
},
"id": "58dd9cf4-8e20-5602-87ef-5eb18cd74636",
"slug": "a"
}
]
}
},
"extensions": {}
}
As you can see the result contains all pages.
What I would like to achieve is to have an "index" page that only show links to the pages within the same directory of the "index" page.
How can I modify the query to return only the current directory's pages?
EDIT:
I made some progress by changing the query to:
query{
allMdx(
sort: {fields: frontmatter___date, order: DESC}
filter: {slug: {regex: "/^sub/[^/]+$/"}}
) {
nodes {
frontmatter {
date(formatString: "D MMMM YYYY")
title
}
id
slug
}
}
}
which gives the result of:
{
"data": {
"allMdx": {
"nodes": [
{
"frontmatter": {
"date": "24 July 2021",
"title": "Sub Page D"
},
"id": "4de57b7f-5e87-520f-ab06-940fb6f91340",
"slug": "sub/d"
}
]
}
},
"extensions": {}
}
But my graphQL is hard-coded. I still do not understand how to pass in a variable dynamically so that it would give different results depending on which slug/directory the page was loaded, for example to have something like:
filter: {slug: {regex: $expression}}
This is the full page code I have so far:
import * as React from 'react'
import { Link, graphql } from 'gatsby'
import Layout from '../../components/layout'
const BlogPage = ({ data, location }) => {
//const slug = location.pathname;
return (
<Layout pageTitle="My Blog Posts">
{
data.allMdx.nodes.map(node => (
<article key={node.id}>
<h2>
<Link to={`/blog/${node.slug}`}>
{node.frontmatter.title}
</Link>
</h2>
<p>Posted: {node.frontmatter.date}</p>
</article>
))
}
</Layout>
)
}
export const query = graphql`
query{
allMdx(
sort: {fields: frontmatter___date, order: DESC}
filter: {slug: {regex: "/^sub/[^/]+$/"}}
) {
nodes {
frontmatter {
date(formatString: "D MMMM YYYY")
title
}
id
slug
}
}
}
`
export default BlogPage
Your query is fetching all data from the MDX (hence the allMdx node), set in the gatsby-source-filesystem so it's getting all title and date from frontmatter, without filtering.
One easy thing that is the straightforward workaround in this scenarios of filtering desired pages is adding a key field in your MDX files so you can filter your query using that field:
query {
allMdx(
filter: {frontmatter: { key: { eq: "index-page"}}}
sort: {fields: frontmatter___date, order: DESC}) {
nodes {
frontmatter {
date(formatString: "MMMM D, YYYY")
title
}
id
slug
}
}
}
So you will be only fetching one-page data.
I am using the following graphql query to get the header from Contentful:
{
header {
name
navigationCollection {
items {
name
}
}
}
}
And this is the result I get:
{
"header"
"name": "Main Header"
"navigationCollection": {
"items" : [
{
"name": "nav1",
},
{
"name": "nav2",
}
]
}
}
I wonder if there is way to simplify the GraphQL query result to produce something like this :
{
"header"
"name": "Main Header"
"navigationItems": [
{
"name": "nav1",
},
{
"name": "nav2",
}
]
}
I cannot mutate a list of objects completely, because only the last element of the array will be mutated.
What already works perfectly is, if I put each element ({play_positions_id: ...}) in the array manually like here:
mutation CreateProfile {
__typename
create_profiles_item(data: {status: "draft", play_positions: [{play_positions_id: {id: "1"}}, {play_positions_id: {id: "2"}}]}) {
id
status
play_positions {
play_positions_id {
abbreviation
name
}
}
}
}
Output:
{
"data": {
"__typename": "Mutation",
"create_profiles_item": {
"id": "1337",
"status": "draft",
"play_positions": [
{
"play_positions_id": {
"id": "1",
"abbreviation": "RWB",
"name": "Right Wingback"
}
},
{
"play_positions_id": {
"id": "2",
"abbreviation": "CAM",
"name": "Central Attacking Midfielder"
}
}
],
}
}
}
Since you can add many of those elements, I defined a variable/argument like here
mutation CreateProfile2($cpppi: [create_profiles_play_positions_input]) {
__typename
create_profiles_item(data: {status: "draft", play_positions: $cpppi}) {
id
status
play_positions {
play_positions_id {
id
abbreviation
name
}
}
}
}
Variable object for above:
"cpppi": {
"play_positions_id": {
"id": "1"
},
"play_positions_id": {
"id": "2
}
}
Output:
{
"data": {
"__typename": "Mutation",
"create_profiles_item": {
"id": "1338",
"play_positions": [
{
"play_positions_id": {
"id": "2",
"abbreviation": "CAM",
"name": "Central Attacking Midfielder"
}
}
],
}
}
}
Schema:
input create_profiles_input {
id: ID
status: String!
play_positions: [create_profiles_play_positions_input]
}
input create_profiles_play_positions_input {
id: ID
play_positions_id: create_play_positions_input
}
input create_play_positions_input {
id: ID
abbreviation: String
name: String
}
At the last both snippets, only the last object with the id "2" will be mutated. I need these to use the defined input type from my backend.
I figured it out. I got it wrong with the brackets in the variable. Here the solution:
"cpppi": [
{
"play_positions_id": {
"id": "1"
}
},
{
"play_positions_id": {
"id": "2"
}
}
]
Let say I have define a dry-validation like this:
class ApplicationContract < Dry::Validation::Contract
config.messages.backend = :i18n
config.messages.load_paths << 'config/errors.yml'
params do
required(:todo).schema do
required(:title).filled(:string)
required(:items).array(:hash) do
required(:name).filled(:string)
end
end
end
end
Here is my config/errors.yml:
vi:
dry_validation:
errors:
rules:
title:
filled?: 'phai duoc dien'
key?: 'ko dc trong'
items:
name:
key?: 'thieu name'
filled?: 'name rong'
In my code, I use it to validate my data:
my_json = create_my_json
v = ApplicationContract.new
result = v.call(my_json)
render json: result.errors(locale: :vi).to_h
If my_json like:
{
"title": "",
"items": [
{
"name": "bbb"
}
]
}
then I got response:
{
"todo": {
"title": [
"phai duoc dien"
]
}
}
You guys can see my validation for field title works fine with locale vi
Now if my json like:
{
"title": "aa",
"items": [
{
"name": ""
}
]
}
then the response is:
{
"todo": {
"items": {
"0": {
"name": [
"translation missing: vi.dry_validation.errors.filled?"
]
}
}
}
}
The validation still works but it can not get my locale message. It show the warning "translation missing: vi.dry_validation.errors.filled?" instead. How can I fix this problem?
Finally I got it. Just remove the node items from config/errors.yml:
vi:
dry_validation:
errors:
rules:
title:
filled?: 'phai duoc dien'
key?: 'ko dc trong'
name:
key?: 'thieu name'
filled?: 'name rong'
I'm trying to get a jsTree working with on demand loading of subnodes. My code is this:
jQuery('#introspection_tree').jstree({
"json_data" : {
"ajax" : {
url : "http://localhost/introspection/introspection/product"
}
},
"plugins" : [ "themes", "json_data", "ui" ]
});
The json returned from the call is
[
{
"data": "Kit 1",
"attr": {
"id": "1"
},
"children": [
[
{
"data": "Hardware",
"attr": {
"id": "2"
},
"children": [
]
}
],
[
{
"data": "Software",
"attr": {
"id": "3"
},
"children": [
]
}
]
]
}
.....
]
Each element could have a lot of children, the tree is going to be big. Currently this is loading the whole tree at once, which could take some time. What do I have to do to implement on-demand-loading of child-nodes when they are opened by the user?
Thanks in advance.
Irishka pointed me in the right direction, but does not fully resolve my problem. I fiddled around with her answer and came up with this. Using two different server functions is done only for clarity. The first one lists all products at top level, the second one lists all children of a given productid:
jQuery("#introspection_tree").jstree({
"plugins" : ["themes", "json_data", "ui"],
"json_data" : {
"ajax" : {
"type": 'GET',
"url": function (node) {
var nodeId = "";
var url = ""
if (node == -1)
{
url = "http://localhost/introspection/introspection/product/";
}
else
{
nodeId = node.attr('id');
url = "http://localhost/introspection/introspection/children/" + nodeId;
}
return url;
},
"success": function (new_data) {
return new_data;
}
}
}
});
The json data returned from the functions is like this (notice the state=closed in each node):
[
{
"data": "Kit 1",
"attr": {
"id": "1"
},
"state": "closed"
},
{
"data": "KPCM 049",
"attr": {
"id": "4"
},
"state": "closed"
},
{
"data": "Linux BSP",
"attr": {
"id": "8"
},
"state": "closed"
}
]
No static data is needed, the tree is now fully dynamic on each level.
I guess it would be nice to display by default first level nodes and then the children will be loaded on demand. In that case the only thing you have to modify is to add "state" : "closed" to the nodes whose child nodes are going to be loaded on demand.
You might wish to send node's id in ajax call so you modify your code
"json_data": {
//root elements to be displayed by default on the first load
"data": [
{
"data": 'Kit 1',
"attr": {
"id": 'kit1'
},
"state": "closed"
},
{
"data": 'Another node of level 1',
"attr": {
"id": 'kit1'
},
"state": "closed"
}
],
"ajax": {
url: "http://localhost/introspection/introspection/product",
data: function (n) {
return {
"nodeid": $.trim(n.attr('id'))
}
}
}
}
From jsTree documentation
NOTE:
If both data and ajax are set the initial tree is rendered from the data string. When opening a closed node (that has no loaded children) an AJAX request is made.
you need to set root elements as tree data on page load and then you will be able to retrieve their children with an ajax request
$("#introspection_tree").jstree({
"plugins": ["themes", "json_data", "ui"],
"json_data": {
//root elements
"data": [{"data": 'Kit 1', "attr": {"id": 'kit1'}} /*, ... */], //the 'id' can not start with a number
"ajax": {
"type": 'POST',
"data": {"action": 'getChildren'},
"url": function (node) {
var nodeId = node.attr('id'); //id="kit1"
return 'yuorPathTo/GetChildrenScript/' + nodeId;
},
"success": function (new_data) {
//where new_data = node children
//e.g.: [{'data':'Hardware','attr':{'id':'child2'}}, {'data':'Software','attr':{'id':'child3'}}]
return new_data;
}
}
}
});
See my answer to a similar question here (the old part) for more details
I spended hours on this problem. Finally i got it that way:
$("#resourceTree").jstree({
"types": {
"default": {
"icon": "fa fa-folder-open treeFolderIcon",
}
},
"plugins": ["json_data", "types", "wholerow", "search"],
"core": {
"multiple": false,
"data": {
"url" : function(node){
var url = "rootTree.json";
if(node.id === "specialChildSubTree")
url = "specialChildSubTree.json";
return url;
},
"data" : function(node){
return {"id" : node.id};
}
}
},
});
rootTree.json:
[
{
"text": "Opened root folder",
"state": {
"opened": true
},
"children": [
{
"id" : "specialChildSubTree",
"state": "closed",
"children":true
}
]
}
]
specialChildSubTree.json:
[
"Child 1",
{
"text": "Child 2",
"children": [
"One more"
]
}
]
So i mark the node that become the parent of the ajax loaded subtree with an id, i watch for in the core configuration.
NOTE:
That node must have the "state" : "closed" parameter and it must have
the parameter "children" : true.
I am using jsTree.js in version 3.3.3
Above solution is all fine. Here I am also providing similar working solution and very simple for lazy loading of nodes using ajax call vakata. When your API works like
https://www.jstree.com/fiddle/?lazy
and for getting any child nodes
https://www.jstree.com/fiddle/?lazy&id=2
for explanation and for complete solution you can have a look at https://everyething.com/Example-of-jsTree-with-lazy-loading-and-AJAX-call
<script type="text/javascript">
$(function () {
$('#SimpleJSTree').jstree({
'core' : {
'data' : {
'url' : "https://www.jstree.com/fiddle/?lazy",
'data' : function (node) {
return { 'id' : node.id };
}
}
}
});
});
</script>