# \[HYPERAPP] hyperapp-tutorial-step-4 (ok)

C:\xampp\htdocs\hyperlist\index.html

```
<!DOCTYPE html>
<html>
<head>
  <title>Hyperapp Tutorial - Step 1</title>
  <link rel="stylesheet" href="style.css" />
  <script type="module" src="./index.js"></script>
</head>
<body>
  <div id="app"></div>
</body>
</html>
```

C:\xampp\htdocs\hyperlist\index.js

```
import { h, app } from "hyperapp"
// -- EFFECTS & SUBSCRIPTIONS --
const fetchJSONData = (dispatch, options) => {
  dispatch(options.onstart)
  fetch(options.url)
    .then(response => response.json())
    .then(data => dispatch(options.onresponse, data))
    .catch(() => dispatch(options.onresponse, {}))
    .finally(() => dispatch(options.onfinish))
}
const intervalSubscription = (dispatch, options) => {
  const interval = setInterval(() => dispatch(options.action), options.time)
  return () => clearInterval(interval)
}
// -- EFFECT CREATORS --
const storyLoader = searchWord => [
  fetchJSONData,
  {
    url: `https://hyperapp.dev/tutorial-assets/stories/${searchWord.toLowerCase()}.json`,
    onresponse: GotStories,
    onstart: [SetFetching, true],
    onfinish: [SetFetching, false],
  },
]
// -- ACTIONS --
const StartEditingFilter = state => ({ ...state, editingFilter: true })
const StopEditingFilter = state => [{
    ...state,
    editingFilter: false,
  },
  storyLoader(state.filter),
]
const SetFilter = (state, word) => ({ ...state, filter: word })
const SelectStory = (state, id) => ({
  ...state, // keep all state the same, except for the following:
  reading: id,
  stories: {
    ...state.stories, //keep stories the same, except for:
    [id]: {
      ...state.stories[id], //keep this story the same, except for:
      seen: true,
    },
  },
})
const GotStories = (state, stories) => ({
  ...state,
  // replace old stories with new,
  // but keep the 'seen' value if it exists
  stories: Object.keys(stories)
    .map(id => [
      id,
      {
        ...stories[id],
        seen: state.stories[id] && state.stories[id].seen,
      },
    ])
    .reduce((o, [id, story]) => ((o[id] = story), o), {}),
  // in case the current story is in the new list as well,
  // keep it selected, Otherwise select nothing
  reading: stories[state.reading] ? state.reading : null,
})
const SetFetching = (state, fetching) => ({ ...state, fetching })
const ToggleAutoUpdate = state => ({
  ...state,
  autoUpdate: !state.autoUpdate,
})
// -- VIEWS ---
const emphasize = (word, string) =>
  string.split(" ").map(x => {
    if (x.toLowerCase() === word.toLowerCase()) {
      return h("em", {}, x + " ")
    } else {
      return x + " "
    }
  })
const storyThumbnail = props =>
  h(
    "li", {
      onclick: [SelectStory, props.id],
      class: {
        unread: props.unread,
          reading: props.reading,
      },
    },
    [
      h("p", { class: "title" }, emphasize(props.filter, props.title)),
      h("p", { class: "author" }, props.author),
    ],
  )
const storyList = props =>
  h("div", { class: "stories" }, [
    // show spinner overlay if fetching
    props.fetching &&
    h("div", { class: "loadscreen" }, [h("div", { class: "spinner" })]),
    h(
      "ul", {},
      Object.keys(props.stories).map(id =>
        storyThumbnail({
          id,
          title: props.stories[id].title,
          author: props.stories[id].author,
          unread: !props.stories[id].seen,
          reading: props.reading === id,
          filter: props.filter,
        }),
      ),
    ),
  ])
const filterView = props =>
  h("div", { class: "filter" }, [
    "Filter:",
    props.editingFilter ?
    h("input", {
      type: "text",
      value: props.filter,
      oninput: [SetFilter, event => event.target.value], // <----
    }) :
    h("span", { class: "filter-word" }, props.filter),
    props.editingFilter ?
    h("button", { onclick: StopEditingFilter }, "\u2713") :
    h("button", { onclick: StartEditingFilter }, "\u270E"),
  ])
const storyDetail = props =>
  h("div", { class: "story" }, [
    props && h("h1", {}, props.title),
    props &&
    h(
      "p", {},
      `Lorem ipsum dolor sit amet, consectetur adipiscing
                    elit, sed do eiusmod tempor incididunt ut labore et
                    dolore magna aliqua. Ut enim ad minim veniam, qui
                    nostrud exercitation ullamco laboris nisi ut aliquip
                    ex ea commodo consequat.`,
    ),
    props && h("p", { class: "signature" }, props.author),
  ])
const autoUpdateView = props =>
  h("div", { class: "autoupdate" }, [
    "Auto update: ",
    h("input", {
      type: "checkbox",
      checked: props.autoUpdate,
      oninput: ToggleAutoUpdate,
    }),
  ])
const container = content => h("div", { class: "container" }, content)
// -- RUN --
app({
  node: document.getElementById("app"),
  init: [{
      editingFilter: false,
      autoUpdate: false,
      filter: "ocean",
      reading: null,
      stories: {},
    },
    storyLoader("ocean"),
  ],
  view: state =>
    container([
      filterView(state),
      storyList(state),
      storyDetail(state.reading && state.stories[state.reading]),
      autoUpdateView(state),
    ]),
  subscriptions: state => [
    state.autoUpdate &&
    !state.editingFilter && [
      intervalSubscription,
      {
        time: 5000, //milliseconds,
        action: StopEditingFilter,
      },
    ],
  ],
})
```

C:\xampp\htdocs\hyperlist\package.json

```
{
  "name": "hyperapp-tutorial-step-2",
  "version": "1.0.0",
  "description": "",
  "main": "index.html",
  "scripts": {
    "start": "parcel index.html --open",
    "build": "parcel build index.html"
  },
  "dependencies": {
    "hyperapp": "2.0.3"
  },
  "devDependencies": {
    "@babel/core": "7.2.0",
    "parcel-bundler": "^1.6.1"
  },
  "keywords": []
}
```

C:\xampp\htdocs\hyperlist\style.css

```
.container {
  font-family: sans-serif;
  color: #666;
  background-color: #ccc;
  width: 100%;
  display: grid;
  grid-template-columns: 10px 340px 10px auto 10px;
  grid-template-rows: 10px 30px 10px 250px 10px 20px 10px;
}
.filter {
  grid-column: 2 / 3;
  grid-row: 2 / 3;
  position: relative;
  line-height: 30px;
  padding-top: 1px;
}
.stories {
  grid-column: 2 / 3;
  grid-row: 4 / 5;
  position: relative;
}
.autoupdate {
  grid-column: 2/3;
  grid-row: 6/7;
}
.story {
  grid-column: 4 / 5;
  grid-row: 2 / 7;
  position: relative;
}
.filter .filter-word {
  font-weight: bold;
  text-transform: uppercase;
  font-size: 16px;
  margin-left: 15px;
}
.filter input[type='text'] {
  position: absolute;
  box-sizing: border-box;
  border: none;
  top: 0;
  left: 50px;
  height: 30px;
  width: 245px;
  padding: 0;
  padding-left: 5px;
  padding-top: 2px;
  font-size: 16px;
  text-transform: uppercase;
  font-weight: bold;
  font-family: sans-serif;
}
.filter button {
  position: absolute;
  top: 0;
  right: 0;
  box-sizing: border-box;
  width: 30px;
  height: 30px;
  font-size: 20px;
  border-radius: 4px;
  background: #ddd;
  border: 1px gray solid;
  box-shadow: 0px 0px 4px #888;
}
.filter button:hover {
  background: lemonchiffon;
}
.filter button:active {
  color: #666;
  box-shadow: none;
}
.stories {
  height: 100%;
}
.stories ul {
  margin: 0;
  padding: 10px;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  list-style-type: none;
  background-color: #fff;
  box-shadow: inset 0px 0px 5px #333;
  border-radius: 4px;
  overflow-y: scroll;
  overflow-x: hidden;
}
.stories li {
  border: solid 1px #ccc;
  border-radius: 6px;
  border-left-width: 7px;
  box-shadow: 0px 0px 5px #ccc;
  margin-bottom: 10px;
}
.stories li.reading {
  background-color: lemonchiffon;
  border-left-color: orange;
}
.stories li.unread {
  border-left-color: cornflowerblue;
}
.stories li:hover {
  background-color: lemonchiffon;
}
.stories p.title {
  margin: 10px 10px 0px 10px;
}
.stories em {
  font-weight: bold;
  font-style: normal;
}
.stories p.author {
  margin: 0px 10px 10px 20px;
  font-style: italic;
  font-family: serif;
}
.stories .loadscreen {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 100;
  background-color: rgba(255, 255, 255, 0.7);
  text-align: center;
}
.spinner {
  display: inline-block;
  width: 64px;
  height: 64px;
  position: absolute;
  top: 40%;
  margin-left: -32px;
}
.spinner:after {
  content: ' ';
  display: block;
  width: 46px;
  height: 46px;
  margin: 1px;
  border-radius: 50%;
  border: 5px solid #fff;
  border-color: #ccc transparent #ccc transparent;
  animation: spinner 1.2s linear infinite;
}
@keyframes spinner {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
.autoupdate {
  text-align: center;
}
.story {
  background: #fff;
  padding: 50px;
  overflow-y: scroll;
  overflow-x: hidden;
  box-shadow: inset 0px 0px 5px #444;
}
.story h1 {
  background-color: limegreen;
    color: #fff;
    margin: -40px -40px 40px -40px;
    padding: 50px 50px 20px 50px;
}
.story p {
    text-align: justify;
    font-family: serif;
    line-height: 1.4em;
}
.story p.signature {
    text-align: right;
    font-style: italic;
    font-size: 1.1em;
}

```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://javascriptuse.gitbook.io/advanced/hyperapp-hyperapp-tutorial-step-4-ok.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
