Vue.js custom select component with v-model - javascript

I want to create a custom select component in Vue.js. Since I need specific options styling, I need to create 'select' made of div's etc that looks and acts like a real html select.
Currently I have something like this:
Vue.component('child', {
template: `<div class="component-container" #click="showOptions = !showOptions">
<div class="component__select">
<span class="component__select--name">Select Fruit</span>
<span class="c-arrow-down" v-if="!showOptions"></span>
<span class="c-arrow-up" v-if="showOptions"></span>
</div>
<ul class="component__select-options" v-if="showOptions" >
<li class="select--option" v-for="option in options">
<label> <input type="checkbox" :value="option"/> {{option.name}}</label>
</li>
</ul>
</div>`,
methods: {
selectOption(option) {
this.$emit('option', option)
}
},
data: () => ({
showOptions: false,
}),
props: ['options']
});
var vm = new Vue({
el: '#app',
data: () => ({
options: [
{id: 0, name: 'Apple'},
{id: 1, name: 'Banana'},
{id: 2, name: 'Orange'},
{id: 2, name: 'Strawberry'},
],
selectedFruit: ''
}),
})
.component__select {
height: 38px;
background-color: #F5F7FA;
border: 1px solid #dddddd;
line-height: 38px;
display: grid;
max-width: 500px;
grid-template-columns: 10fr 1fr;
}
.component__select--name {
font-size: 0.8rem;
padding: 0 0 0 25px;
cursor: pointer;
}
.c-arrow-down {
justify-self: end;
}
.component__select-options {
max-height: 180px;
border: 1px solid #dddddd;
border-top: none;
overflow: auto;
position: absolute;
z-index: 1500;
max-width: 500px;
width: 500px;
margin: 0;
padding: 0;
}
.select--option {
height: 35px;
display: grid;
align-content: center;
padding: 0 0 0 25px;
background-color: #f5f5fa;
border-bottom: 1px solid #dddddd;
}
.select--option:last-child {
border-bottom: none;
}
.select--option:nth-child(2n) {
background-color: #ffffff;
}
.select--option input{
display: none;
}
.single-option {
height: 55px;
background-color: #2595ec;
font-size: 0.8rem;
border: 1px solid red;
}
.cust-sel {
width: 200px;
height: 38px;
background-color: #f5f5fa;
border: 1px solid #dddddd;
}
.cust-sel:focus {
outline-width: 0;
}
<html>
<head>
<title>An example</title>
</head>
<body>
<div id="app">
<span> This is parent component</span>
<p>I want to have data from select here: "{{selectedFruit}}"</p>
<child :options="options" v-model="selectedFruit"></child>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</body>
</html>
But my problem is now how to return data from child to parent component using v-model on child component.
(I know I could emit data from child component and do:
<custom-select :options="someOptions" #selected="setSelectedOption"/>
but I need it to be reusable and writing more and more methods to retrieve data from every select in parent component is not exactly how it should work I think.)
Also I need to have an entire object returned, not only ID. (that's why i've got :value="option")
Any ideas?

As Vue Guide said:
v-model is essentially syntax sugar for updating data on user input
events, plus special care for some edge cases.
The syntax sugar will be like:
the directive=v-model will bind value, then listen input event to make change like v-bind:value="val" v-on:input="val = $event.target.value"
So for your use case, you need to create one prop=value, then emit the selected option with event=input.
Like below demo (bind/emit the whole option object):
Vue.config.productionTip = false
Vue.component('child', {
template: `<div class="component-container" #click="showOptions = !showOptions">
<div class="component__select">
<span class="component__select--name">{{value ? value.name : 'Select Fruit'}}</span>
<span class="c-arrow-down" v-if="!showOptions"></span>
<span class="c-arrow-up" v-if="showOptions"></span>
</div>
<ul class="component__select-options" v-if="showOptions" >
<li class="select--option" v-for="option in options" #click="selectOption(option)">
<label> <input type="checkbox" :value="option"/> {{option.name}}</label>
</li>
</ul>
</div>`,
methods: {
selectOption(option) {
this.$emit('input', option)
}
},
data: () => ({
showOptions: false
}),
props: ['options', 'value']
});
var vm = new Vue({
el: '#app',
data: () => ({
options: [
{id: 0, name: 'Apple'},
{id: 1, name: 'Banana'},
{id: 2, name: 'Orange'},
{id: 2, name: 'Strawberry'},
],
selectedFruit: ''
}),
})
.component__select {
height: 38px;
background-color: #F5F7FA;
border: 1px solid #dddddd;
line-height: 38px;
display: grid;
max-width: 500px;
grid-template-columns: 10fr 1fr;
}
.component__select--name {
font-size: 0.8rem;
padding: 0 0 0 25px;
cursor: pointer;
}
.c-arrow-down {
justify-self: end;
}
.component__select-options {
max-height: 180px;
border: 1px solid #dddddd;
border-top: none;
overflow: auto;
position: absolute;
z-index: 1500;
max-width: 500px;
width: 500px;
margin: 0;
padding: 0;
}
.select--option {
height: 35px;
display: grid;
align-content: center;
padding: 0 0 0 25px;
background-color: #f5f5fa;
border-bottom: 1px solid #dddddd;
}
.select--option:last-child {
border-bottom: none;
}
.select--option:nth-child(2n) {
background-color: #ffffff;
}
.select--option input{
display: none;
}
.single-option {
height: 55px;
background-color: #2595ec;
font-size: 0.8rem;
border: 1px solid red;
}
.cust-sel {
width: 200px;
height: 38px;
background-color: #f5f5fa;
border: 1px solid #dddddd;
}
.cust-sel:focus {
outline-width: 0;
}
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<span> This is parent component</span>
<p>I want to have data from select here: "{{selectedFruit}}"</p>
<child :options="options" v-model="selectedFruit"></child>
</div>

When using v-model on custom component all you need is to declare a prop named 'value' and when you need the component to chance it emit an 'input' event.
Something like this:
<template>
<form #submit.prevent="$emit('onSearch',val)" class="form-perfil">
<div class="form-group col-md-12">
<input v-model="val" #input="$emit('input',val)"
placeholder="filtrar resultados" class="form-control">
</div>
</form>
</template>
<script>
module.exports = {
name: "CaixaFiltro",
props: ["value"],
data: _ => ({ val: "" }),
created() {
this.val = this.value
}
}
</script>
Then you can use (after register the component) it like this:
<caixa-filtro v-model="textoBusca" #onSearch="listar"></caixa-filtro>
You can find more detailed info there:

Related

REACT JS - Working on eshop-like project, got stuck with product filters

So hello. I've been working on my own stuff as I believe it is the best way to learn things. So I got stuck, I am quite new to this react thing. I got this code, as you can see I have few checkboxes there, and what I want to achieve is to check the box to filter (hide) products from the array. I kinda got to the point where I don't know what should I do next, I know I need to put something into constructor, but I can't really figure out what. Can you please help me with that? Thanks!
class Shop extends React.Component {
constructor(props) {
super(props);
this.state = {
//I should put something here?
}
}
render(){
let checkbox = (a) => {
this.setState({cpu: a.target.checked});
}
return (<div>
<input type="checkbox" onChange={checkbox} name="cpu" id="cpu"></input>
//I will do these later, so far I'd be happy to get cpu filter to work.
<input type="checkbox" name="gpu" id="gpu"></input>
<input type="checkbox" name="psu" id="psu"></input>
<input type="checkbox" name="mb" id="mb"></input>
<input type="checkbox" name="ram" id="ram"></input>
<input type="checkbox" name="case" id="case"></input>
{products.filter(product =>{
if (true) {
return true;
}
}).map((shop) =>
<>
<div id="prodinfo">
<p id="pname">{shop.name}</p>
<p id="pprice">{shop.price}</p>
<img src={shop.image} id="pimg" alt=""></img>
</div>
</>)} </div>);
}
}
ReactDOM.render(
<Shop/>,
document.getElementById('maincontent')
);
.group:after {
content: "";
display: table;
clear: both;
}
/* HEADER */
header {
background-color: rgb(57,184,231);
height: 9em;
border-bottom: 2px solid blue;
}
.mainheader {
margin: 0 auto;
width: 70em;
}
.socialnetworks {
display: flex;
justify-content: flex-end;
margin: -7px 0 0 0;
width: 100%;
background-color: rgb(0,170,203);
height: 20px;
}
.socialnetworks i {
padding-right: 20px;
color: white;
font-size: 20px;
}
.socialnetworks i:first-child:hover {
color: rgb(66, 103, 178);
cursor: pointer;
}
.socialnetworks i:last-child:hover {
color: red;
cursor: pointer;
}
.socicons {
padding-right: 410px;
}
.socialnetworks i:last-child {
padding: 0;
}
.logo {
position: relative;
top:0;
left:0;
max-width: 18%;
font-size: 60px;
color: white;
}
.logo span {
font-weight: bolder;
}
.menu {
text-align: center;
}
.menu span {
margin-right: 15px;
padding: 10px 10px 10px 10px;
font-size: 25px;
font-weight: bolder;
}
.menu span:hover {
border-radius: 5px;
background-color: rgb(33, 97, 194);
cursor: pointer;
}
.menu a {
text-decoration: none;
color: whitesmoke;
}
.menu a:last-child {
padding: 0;
}
.basket {
position: absolute;
top: 65px;
right: 60px;
}
.basket span:hover {
background-color: rgb(0, 140, 255);
cursor: pointer;
}
.basket span {
padding: 5px 5px 5px 5px;
border: 1px solid grey;
border-radius: 5px;
background-color: rgb(0, 41, 128);
color: whitesmoke;
}
/* MAIN_CONTENT */
#maincontent {
padding-top: 10em;
width: 1251px;
margin: 0 auto;
}
#prodinfo {
display: inline-block;
width: 400px;
height: 300px;
border: 1px solid black;
border-radius: 5px;
margin: 0 15px 15px 0;
}
#pname {
text-align: center;
font-size: 30px;
font-weight: bolder;
}
#pprice {
position: relative;
top: 165px;
left: 60px;
font-size: 20px;
}
#pimg {
position: relative;
bottom: 40px;
left: 110px;
height: 160px;
width: 200px;
}
#pprice::after {
content: "€";
}
#prodfilters {
text-align: center;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://kit.fontawesome.com/a2faab1b70.js" crossorigin="anonymous"></script>
<script src="database.js"></script>
<script src="functions.js" type="text/babel"></script>
<script src="https://unpkg.com/#babel/standalone/babel.min.js"></script>
<script src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script>
<link rel="stylesheet" href="style.css">
<title>PCzone</title>
</head>
<body>
<header>
<div class="socialnetworks">
<div class="socicons">
<i class="fab fa-facebook-square"> Facebook</i>
<i class="fab fa-youtube"> Youtube</i>
</div>
</div>
<div class="mainheader">
<div class="logo">
<span>PC</span>zone
</div>
<div class="menu">
<span>Domov</span>
<span>Zľavené produkty</span>
<span>O nás</span>
</div>
<div class="basket">
<span><i class="fas fa-shopping-basket">Nákupný košík</i></span>
</div>
</div>
</header>
<div id="prodfilters">
<p>Filter produktov</p>
</div>
<div id="maincontent">
</body>
</html>
class Shop extends React.Component {
constructor(props) {
super(props);
this.state = {
cpu: false,
gpu: false,
psu: false,
mb: false,
ram: false,
case: false,
}
}
render(){
let checkbox = (a) => {
this.setState({ [a.target.name]: a.target.checked });
}
return <div>
{products.map(product=> {
return <input
type="checkbox"
onChange={checkbox}
name={product.type}
id={product.type}
/>
})}
{products.filter(product => {
return this.state[product.type];
}).map((shop) =>
<div id="prodinfo">
<p id="pname">{shop.name}</p>
<p id="pprice">{shop.price}</p>
<img src={shop.image} id="pimg" alt="" />
</div>)}
</div>;
Okay, so.. First of all, you need to set initial state to be able to trigger re-renders in you components (this what #alexsc _'s answer was about).
Second of all, if you're trying to filter on an array of objects, you must have a field that you can use for that (note that I added a type variable that would contain the type of cpu or gpu, etc.. for each product). The React way to render multiple elements with similar values are usually done with mapping the related array (notice the mapping of products that returns an input element).
Following this logic, the third modification I made on your code was the filtering of products. This might not make any sense whatsoever, but when you click on an input element, it will trigger a re-render, due to the modification of a state member. This is why you need to have initial state and this is why I put the line, this.state[product.type] in the filter. With this, React will detect a change in state and will attempt to re-render your component, which calls the filter method again with updated values.
To make it more clear, let's say you filter by cpu. You'll click on the input which says cpu. This will set the cpu state variable to true. React detects that the state has changed so it attempts a re-render. Then it'll call the filter method on your products array again and this.state[product.type] will eventually be this.state['cpu'] which will evaluate to true.
NOTE: If you're unfamiliar with any of the used syntax, you should checkout the docs
In
this.state = {
//I should put something here?
}
You should put the initial value of cpu, in this case i think you want to filter the products when the user checks the checkbox, so the initial value of the state variable is gonna be false, like this:
this.state = {
cpu:false
}
So when the value of cpu is true you should filter all the products that are an cpu.

Drop-down list with bullets

I want to create a drop-down list with bullets using Angular 2 and JavaScript & CSS. I created a drop-down list but i couldn't able to create bullets in list.
This can't be achieved by jQuery, Bootstrap. Any suggestions?
Here is my Code.
dropdownList.component.ts
import { Component} from '#angular/core';
import{FormsModule } from '#angular/forms';
import {Option} from './option';
#Component({
selector: 'app-dropdown',
templateUrl: './dropdown.component.html',
styleUrls: ['./dropdown.component.css']
})
export class DropdownComponent {
selectedItem:Option= new Option(1,'../../assets/blue2.png','option1');
options= [
new Option(1,'../../assets/blue2.png','option1'),
new Option(2,'option2'),
new Option(3,'option3'),
new Option(4,'option4')
];
// OPtion.ts
export class Option{
constructor(public id?: number, public img?:string, public name?:
string )
{
}
}
// component.html:
<select class="dropdown" id="style">
<option *ngFor="let Option of options" value={{Option.id}}
class="dropdownList">{{Option.name}}{{Option.img}}
</option>
</select>
CSS can make anything looked like other stuff. I implement a really simple dropdown but only really basic features. To complete this dropdown you still need to implement ngModel and label/value dropdown items. But I think this have what you descibed already.
import { Component } from '#angular/core'
#Component({
selector: 'demo-dropdown',
template: `
<div class="ui-dropdown">
<label class="ui-dropdown-label">{{selectedItem}}</label>
<span class="ui-dropdown-button" (click)="showList = !showList">﹀</span>
<ul class="ui-dropdown-list" [class.active]="showList">
<li *ngFor="let item of items" (click)="onItemSelected($event)">{{item}}</li>
</ul>
</div>
`,
styles: [`
*{
box-sizing: border-box;
}
.ui-dropdown{
background: #fff;
border: 1px solid black;
border-radius: 3px;
display: inline-flex;
flex-wrap: wrap;
padding: 0;
user-select: none;
width: 250px;
}
.ui-dropdown-label{
flex: 1 1 0;
padding: 0.2em 0.5em;
}
.ui-dropdown-button{
border-left: 1px solid black;
cursor: pointer;
flex: 0 0 20px;
padding-top: 0.2em 0.2;
}
.ui-dropdown-list{
background: #fff;
border: 1px solid black;
border-radius: 3px;
display: none;
flex: 0 0 100%;
margin: 0;
margin-top: 25px;
padding: 0;
position: absolute;
width: 250px;
}
.ui-dropdown-list.active{
display: block;
}
.ui-dropdown-list>li{
cursor: pointer;
list-style: none;
}
.ui-dropdown-list>li::before{
content: '.';
}
.ui-dropdown-list>li:hover{
background: #eee;
}
`]
})
export class DemoDropdown{
showList:boolean = false;
selectedItem:string = null;
items: OptionItem[] = ["option 1","option 2"];
constructor() { }
onItemSelected(e){
this.selectedItem = e.target.innerText;
this.showList = false;
}
}

CSS, display blocks to different sizes

I got main block, for example it it 400px,
in this block i could have a lot of block, and i have 3 situations
i need to display score on 1 line.
1) When i got 100% - it shold be in the corner of green block
2) When it got a small size, Percentages should have "margin-left: 15px " to text, dont know, how to explain
3) And when it is for example 50% it should be after the bar
And the main problem, all text sholud be in this background, after this i will add onClick function that will close and open Teams(this you will see in the demo), so this block with all background will be receizing onClick, so all DIVs in this block with background
https://codepen.io/anon/pen/pWXaej
class Application extends React.Component {
percentsToPx ( score ) {
return score *4
}
render() {
const examples = [
{
name: 'Example 1',
score: 100,
teams: [ { name: 'Example 1' }, { name: 'Example 1' }, { name: 'Example 1' } ]
},
{
name: 'Example 2',
score: 55,
teams: [ { name: 'Example 2' }, { name: 'Example 2' }, { name: 'Example 2' } ]
},
{
name: 'Example 1',
score: 4,
teams: [ { name: 'Example 3' }, { name: 'Example 3' }, { name: 'Example 3' } ]
}
]
return <div className='project'>
{examples.map( ( it, index) => {
const size = this.percentsToPx( it.score)
return (
<div className='projectTab'>
<div style={{ display: 'inline-block' }}>
<div style={{ width: size, background: 'green', display: 'inline-block' }} className='projectBlock'>
<div className='projectText'>
<h1 className='projectTextMain'>{it.name}</h1>
<div>
{it.teams.map( ( team, index ) => {
return (
<div style={{ marginLeft: 20 }} key={index}>
<h2 style={{
color: 'black',
whiteSpace: 'nowrap',
cursor: 'default'
}}>{team.name}</h2>
</div>
);
} )}
</div>
</div>
</div>
<div style={{ width: it.score, display: 'inline-block' }}></div>
</div>
<h2 className='projectTextPercents'>{it.score}%</h2>
</div>)
})}
</div>;
}
}
/*
* Render the above component into the div#app
*/
React.render(<Application />, document.getElementById('app'));
What I want to do
What I have
and styles
.projects {
display: flex;
width: 400px;
flex-direction: column;
align-items: stretch;
margin-top: 10px;
&-tab {
border-top: 1px solid grey;
&:hover {
background: white;
}
&:last-child {
border-bottom: 1px solid grey;
}
}
&-block {
display: flex;
align-items: baseline;
}
&-text {
display: inline-block;
minHeight: 100%;
height: 100%;
margin-left: -10px;
&-main {
color: black;
white-space: nowrap;
display: inline-block;
cursor: default;
margin-left: 30px;
}
&-percents {
display: inline-block;
color: grey;
margin-left: 10px;
vertical-align: top;
cursor: default;
}
}
}
THANKS FOR YOUR HELP
I would suggest the following approach: as I see, you generate elements' width and put it right in the markup. So, you could generate also an attribute (lets say data-w) for an element with value based on the element's width. Then you could target those elements via css. In such a case you need to generate on the server-side some small styles as well for the cases when width is not equal to 100%:
div {
padding: 10px;
border: 1px solid green;
min-height: 38px;
color: white;
background-color: orange;
margin: 10px 0px;
position: relative;
box-sizing: border-box;
}
div[data-w="full"]:after {
position: absolute;
top: calc(50% - 8px);
white-space: nowrap;
right: 5%;
content: '100%';
}
/* -- Generate the corresponding styles on the server-side and put them right in the markup for every item with width not equal to 100% -- */
div[data-w="average1"]:after {
position: absolute;
top: calc(50% - 8px);
white-space: nowrap;
color: black;
right: -45px;
content: '50%';
}
div[data-w="average2"]:after {
position: absolute;
top: calc(50% - 8px);
white-space: nowrap;
color: black;
right: -45px;
content: '47%';
}
div[data-w="small"]:after {
position: absolute;
top: calc(50% - 8px);
white-space: nowrap;
color: black;
right: -115px;
content: 'example 3 16%';
}
/* -- End of note -- */
<div data-w="full" style="width: 300px">example 1</div>
<div data-w="average1" style="width: 150px">example 2.1</div>
<div data-w="average2" style="width: 140px">example 2.2</div>
<div data-w="small" style="width: 50px"></div>

How to access a method from vuejs component?

I am building a Tic Tac Too game with vue.js framework. I have declared a vue component called grid-item, when this item is clicked I want it to call the handleClick method.
when I run the code bellow it logs to the console that the handleClick method is not defined.
How to fix the problem and get access to this method from the component ?
// vue components
Vue.component("grid-item", {
template: "#grid-item",
data: function() {
return {
sign: "X",
owner: ""
}
}
})
// vue instance
new Vue({
el: "#app",
data: {
matriceSize: 3,
},
methods: {
handleClick: function() {
alert("checked");
}
}
})
* {
box-sizing: border-box;
}
#game-box {
width: 150px;
display: block;
margin: 0px auto;
padding: 0px;
background: green;
}
.grid-item {
display: inline-block;
width: 33.333%;
height: 50px;
background: yellow;
margin: 0px;
text-align: center;
line-height: 50px;
border: 1px solid
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<div id="game-box">
<grid-item v-for="n in 9"></grid-item>
</div>
</div>
<template id="grid-item">
<div class="grid-item" #click="handleClick"></div>
</template>
You are getting this error as you have defined handleClick method in component : app but you are using this in the template of grid-item, where it is not defined.
Scope of vue methods is limited to the instance they have been defined.
// vue components
Vue.component("grid-item", {
template: "#grid-item",
data: function() {
return {
sign: "X",
owner: ""
}
},
methods: {
handleClick: function() {
alert("checked");
}
}
})
// vue instance
new Vue({
el: "#app",
data: {
matriceSize: 3,
}
})
* {
box-sizing: border-box;
}
#game-box {
width: 150px;
display: block;
margin: 0px auto;
padding: 0px;
background: green;
}
.grid-item {
display: inline-block;
width: 33.333%;
height: 50px;
background: yellow;
margin: 0px;
text-align: center;
line-height: 50px;
border: 1px solid
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<div id="game-box">
<grid-item v-for="n in 9"></grid-item>
</div>
</div>
<template id="grid-item">
<div class="grid-item" #click="handleClick"></div>
</template>

Custom / Styled prompt window?

I'm trying to make a 'Choose your Adventure' game, and I want to know if it's possible to make a styled/custom 'Prompt' window, and if it can be not opened up as a 'prompt' window, but have the prompt and user input in a selected HTML box? This is what I mean.
If my HTML has
HTML
<html>
<body>
<textarea class="prompt" disabled="1"></textarea><br>
<input class="input" type="text" value="inputText"></input>
<input type="submit" value="userInput"></input>
</body>
</html>
and CSS of
CSS
.prompt {
width: 300px;
height: 500px;
background: black;
color: #FFA500;
}
and JavaScript (I probably will mess up the code)
JavaScript
var prompt = document.getElementByClassName("prompt");
var choice = prompt("What is your choice? CHOICE1, CHOICE2, or CHOICE3?").toUpperCase();
prompt.innerHTML = choice;
and I hope to get something like the prompt not showing up a dialogue window but instead putting the prompt text into the textarea, and the user put in their choice with the input, then submit it by the submit button. How could I get it so that the prompt window instead outputs the question/text to the textarea, and the user puts in their answer via the input text field, and submitting it via the input submit button, and it works like normal. Is this even possible?
If not, is it at least possible to style the prompt dialogue box itself? Here's my code so far.
function fight() {
var intro = prompt("You are a hero who saved your town from a dragon attack years ago. You had fun murdering that dragon, but sadly no dragon has attacked since. Just when all hope is lo, you hear the sirens ring through the city. You know what that means. Do you PREPARE, or IGNORE THE SIRENS?").toUpperCase();
switch(intro) {
case 'PREPARE':
if(intro === "PREPARE") {
prompt("You decided to " + intro + ". You need to choose what you will do. Keep in mind, the more activities you do, the less energy you have! You only have 3 days to prepare! What do you do? Do you SEARCH ARMOR STAND, SEARCH WEAPON STAND, GO TO MERCHANT, FIGHT DRAGON, TRAIN, or SLEEP?").toUpperCase();
}
}
}
#import url(http://fonts.googleapis.com/css?family=Permanent+Marker);
html, body {
background: #000;
margin: 0;
padding: 0;
}
#wrap {
width: 760px;
margin-left: auto;
margin-right: auto;
}
.container {
position: relative;
top: 50px;
margin-left: auto;
margin-right: auto;
width: 570px;
height: 350px;
border: 6px ridge orange;
padding: 0;
}
.container img {
position: absolute;
bottom: 0px;
width: 570px;
height: 350px;
z-index: -1;
}
p.intro {
color: black;
text-shadow:
-1px -1px 0 #FFF,
1px -1px 0 #FFF,
-1px 1px 0 #FFF,
1px 1px 0 #FFF;
}
h2.header {
text-shadow:
-1px -1px 0 #FFA500,
1px -1px 0 #FFA500,
-1px 1px 0 #FFA500,
1px 1px 0 #FFA500;
}
.box {
float: left;
min-width: 567px;
min-height: 350px;
}
.box h2 {
font-family: 'Permanent Marker', cursive;
font-size: 200%;
text-align: center;
}
.box p {
font-family: 'Permanent Marker', arial;
text-align: center;
}
.box a {
position: absolute;
left: 165px;
display: inline-block;
border: 3px groove #000;
border-radius: 5px;
background: red;
margin-left: auto;
margin-right: auto;
width: 225px;
height: 75px;
font-family: 'Permanent Marker', cursive;
color: #FFA500;
text-shadow:
-1px -1px 0 #000,
1px -1px 0 #000,
-1px 1px 0 #000,
1px 1px 0 #000;
text-align: center;
}
.battles img {
}
<html>
<body>
<div id="wrap">
<div class="box">
<div class="container">
<h2 class="header">Dragon Slayer - REBORN!</h2>
<p class="intro">You are a hero who saved your town from a dragon attack years ago. You had fun murdering that dragon, but sadly no dragon has attacked since. Just when all hope is lost, you hear the sirens ring through the city. You know what that means.</p>
<br>BEGIN!
<img class="scenario" src="http://www.thegaminghideout.com/school/stage1.png">
<div class="battles">
</div>
</div>
</div>
</div>
</body>
</html>
Here is an example using jQuery. jQuery is useful for manipulating DOM elements. Instead of selecting an object by doing:
document.getElementById('someid');
You would select the element as you would in CSS:
$('#someid);
In my adventure game example, I used a JSON object to contain my story and its paths. The story object is a map of path id (e.g. 'intro', 'choose_weapon') to a scenario object. This helps to organize your story.
I used buttons instead of input fields for the options since making the user input their choices gets pretty annoying.
// Contains the story and paths
var story = {
intro: {
prompt: 'It is 12am and you are starving. It\'s too late to order delivery. You know what that means.',
options: [{
name: 'Fight',
path: 'choose_weapon'
}, {
name: 'Starve',
path: 'die_starve'
}]
},
choose_weapon: {
prompt: 'Choose your weapon!',
options: [{
name: 'Knife',
path: 'die_cut'
}, {
name: 'Toaster',
path: 'toast'
}]
},
toast: {
prompt: 'You toast some bread. What do you do next?',
options: [{
name: 'Eat it!',
path: 'eat'
}, {
name: 'Slather on some peanut butter!',
path: 'peanut_butter'
}]
},
peanut_butter: {
prompt: 'There is now peanut butter on your bread. Excellent choice. What do you do next?',
options: [{
name: 'Eat it!',
path: 'eat'
}, {
name: 'Throw it away',
path: 'die_starve'
}]
},
eat: {
prompt: 'It was delicious! You are no longer hungry.',
options: [{
name: 'Start Again',
path: 'intro'
}]
},
die_cut: {
prompt: 'You accidentally cut yourself and bleed to death.',
options: [{
name: 'Start Again',
path: 'intro'
}]
},
die_starve: {
prompt: 'You have died of hunger!',
options: [{
name: 'Start Again',
path: 'intro'
}]
}
}
/**
* Chosen option is an object with properties {name, path}
*/
function display_scenario(chosen_option) {
var option_name = chosen_option.name;
var option_path = chosen_option.path;
var scenario = story[option_path];
// Clear the #prompt div and the #options div
$('#prompt').empty();
$('#options').empty();
// Create a <p> to display what the user has chosen if option_name is not null and append it to the #prompt <div>
if (option_name) {
$('<p>').html('You have chosen <b>' + option_name + '</b>').appendTo('#prompt');
}
// Append the scenario's prompt
$('<p>').html(scenario.prompt).appendTo('#prompt');
// Append the options into the #options <div>
// We want to loop through all the options and create buttons for each one. A regular for-loop would not suffice because adding a button is not asynchronous. We will create an asynchronous loop by using recursion
function add_option_button(index) {
if (index === scenario.options.length) {
// Base case
return;
}
var option = scenario.options[index];
// Create a <button> for this option and append it to the #options <div>
$('<button>')
.html(option.name)
.click(function(e) {
// This is an onclick handler function. It decides what to do after the user has clicked on the button.
// First, prevent any default thing that the button is going to do, since we're specifying our own action for the button
e.preventDefault();
// We'll want to call display_scenario() with this option
display_scenario(option);
})
.appendTo('#options');
// Add the next option button
add_option_button(index + 1);
}
add_option_button(0);
}
// This function waits until the document is ready
$(document).ready(function() {
// Start the story
display_scenario({
name: null,
path: 'intro'
});
});
#import url(http://fonts.googleapis.com/css?family=Open+Sans:400,700);
* {
margin: 0px;
padding: 0px;
color: #32363F;
font: 18px 'Open Sans', sans-serif;
border: none;
outline: none;
box-sizing: border-box;
}
html {
display: table;
width: 100%;
height: 100%;
}
body {
display: table-cell;
background: #32363F;
vertical-align: middle;
}
#wrapper {
margin: 40px;
background: #D6C2A3;
width: calc(100% - 80px);
}
h1 {
display: block;
padding: 20px 20px 12px;
font: 700 36px 'Open Sans', sans-serif;
background: #E84949;
color: #FAFAFA;
text-transform: uppercase;
}
#prompt {
padding: 20px;
}
#prompt p {
padding-bottom: 8px;
}
#prompt p b {
font-weight: 700;
}
#options {
display: flex;
padding: 0px 20px 28px;
text-align: center;
}
#options button {
margin: 0px 8px;
padding: 8px 20px;
background: #C2AE8F;
width: 100%;
cursor: pointer;
}
#options button:hover,
#options button:active {
background: #E84949;
color: #FAFAFA;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="wrapper">
<h1>Food Adventure</h1>
<div id="prompt"></div>
<div id="options"></div>
</div>

Categories

Resources