I am building a Dashboard where you are able to add, delete, move and resize Panels within a grid using GridstackJS. I am filling these Panels with different things. In this Example I am using a Highchart. For saving and restoring the position and Size of the Panels I use the standard serialization of GridstackJS.
My problem now is to save and restore the Content inside the Panels.
Is there an elegant way to save the Content and apply it to the correct Panel when restoring?
JSFiddle
HTML
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/gridstack#0.5.5/dist/gridstack.css" />
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/gridstack#0.5.5/dist/gridstack.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/gridstack#0.5.5/dist/jquery-ui.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/gridstack#0.5.5/dist/gridstack.jQueryUI.js"></script>
<script src="https://code.highcharts.com/highcharts.js"></script>
<button id="save-grid" class="btn btn-primary">Save Grid</button>
<button id="load-grid" class="btn btn-primary">Load Grid</button>
<button id="delete-grid" class="btn btn-primary">Delete Grid</button>
<div class="row">
<div class="col-sm-12">
<div class="grid-stack ui-droppable">
<div class="dragbox grid-stack-item ui-draggable ui-resizable" data-gs-id="draggable">
<h2 class="dragbox-header">Chart 1</h2>
<div class="dragbox-content">
<div class="text-center"> Item 1</div>
</div>
</div>
<div class="dragbox grid-stack-item ui-draggable ui-resizable" data-gs-id="draggable">
<h2 class="dragbox-header">Chart 2</h2>
<div class="dragbox-content"></div>
</div>
<div class="dragbox grid-stack-item ui-draggable ui-resizable" data-gs-id="draggable" data-gs-width="4" data-gs-height="4">
<h2 class="dragbox-header" id="testChartHeader">Chart 3</h2>
<div class="text-center" id="testChart"></div>
</div>
</div>
</div>
</div>
CSS
.dragbox {
margin: 5px 2px 20px;
background: #fff;
position: absolute;
border: 1px solid #ddd;
border-radius: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
}
.dragbox-header {
margin: 0;
font-size: 12px;
padding: 5px;
background: #f0f0f0;
color: #000;
border-bottom: 1px solid #eee;
font-family: Verdana;
cursor: move;
position: relative;
}
.dragbox-content {
display: block;
background: #fff;
margin: 5px;
font-family: 'Lucida Grande', Verdana;
font-size: 0.8em;
line-height: 1.5em;
}
#testChart {
height: 200px;
}
.placeholder {
background: lightgray;
border: 1px dashed #ddd;
border-radius: 5px;
}
JavaScript
$(function() {
var options = {
draggable: {handle: '.dragbox-header', scroll: false, appendTo: 'body'},
placeholderClass: "placeholder",
acceptWidgets: true,
cellHeight: 60
};
$('.grid-stack').gridstack(options);
new function () {
this.serializedData = [
];
this.grid = $('.grid-stack').data('gridstack');
this.loadGrid = function () {
this.grid.removeAll();
var items = GridStackUI.Utils.sort(this.serializedData);
items.forEach(function (node, i) {
this.grid.addWidget($('<div class="grid-stack-item ui-draggable ui-resizable" data-gs-id="draggable"><div class="dragbox grid-stack-item-content ui-draggable-handle"><div class="dragbox-header">Chart ' + (i + 1) + '</div></div></div>'), node);
}, this);
return false;
}.bind(this);
this.saveGrid = function () {
this.serializedData = $('.grid-stack > .grid-stack-item').map(function (i, el) {
el = $(el);
var node = el.data('_gridstack_node');
return {
x: node.x,
y: node.y,
width: node.width,
height: node.height
};
}).toArray();
return false;
}.bind(this);
this.clearGrid = function () {
this.grid.removeAll();
return false;
}.bind(this);
$('#save-grid').click(this.saveGrid);
$('#load-grid').click(this.loadGrid);
$('#delete-grid').click(this.clearGrid);
};
});
var chart = Highcharts.chart('testChart', {
chart: {
animation: false,
type: 'bar'
},
plotOptions: {
series: {
animation: false,
}
},
title: {
text: 'Fruit Consumption'
},
xAxis: {
categories: ['Apples', 'Bananas', 'Oranges']
},
yAxis: {
title: {
text: 'Fruit eaten'
}
},
series: [{
name: 'Jane',
data: [1, 0, 4]
}, {
name: 'John',
data: [5, 7, 3]
}]
});
$('.grid-stack').on('change', function(event, items) {
var chartContainer = chart.renderTo;
$(chartContainer).css(
'height',
$(chartContainer.parentElement).height() - $('#testChartHeader').height()
);
chart.reflow();
});
You need to create a chart again in the loadGrid function:
this.loadGrid = function() {
this.grid.removeAll();
var items = GridStackUI.Utils.sort(this.serializedData);
items.forEach(function(node, i) {
this.grid.addWidget($('<div class="grid-stack-item ui-draggable ui-resizable" data-gs-id="draggable"><div class="dragbox grid-stack-item-content ui-draggable-handle"><div class="dragbox-header">Chart ' + (i + 1) + '</div>' + (i === 2 ? '<div id="testChart"></div>' : '') + '</div></div>'), node);
}, this);
chart = createChart();
return false;
}.bind(this);
Live demo: https://jsfiddle.net/BlackLabel/pjy8b950/
Related
I am a beginner to Vue.js. I have 2 components, one as rows and the second one is the sum of all rows above. The problem is that the sums are not updated automatically, even if they are set as computed.
Find below my code :
let s = []
Vue.component('subitem-row', {
props: ['subitem', 'crt', 'si'],
template: `
<tr>
<td>
<div class="form-group" v-if="crt == si">
<label>EPE</label>
<input v-model="subitem.eprice" #change="calculateSubitem();">
</div>
<span v-else>{{subitem.eprice}}</span>
</td>
<td>
<div class="form-group" v-if="crt == si" #change="parseExpresion(); calculateSubitem();">
<label>Anzahl</label>
<input v-model="subitem.qtytext">
</div>
<span v-else>{{subitem.qtytext}}</span>
</td>
<td>
<div class="form-group" v-if="crt == si">
<label>Faktor</label>
<input v-model="subitem.factor" #change="calculateSubitem();">
</div>
<span v-else>{{subitem.factor}}</span>
</td>
<td>
<div class="form-group" v-if="crt == si">
<label>EK</label>
<input v-model="subitem.tp1" readonly>
</div>
<span v-else>{{subitem.tp1}}</span>
</td>
<td>
<div class="form-group" v-if="crt == si">
<label>Marge</label>
<input v-model="subitem.margin" readonly>
</div>
<span v-else>{{subitem.margin}}</span>
</td>
<td>
<div class="form-group" v-if="crt == si">
<label>VK</label>
<input v-model="subitem.tp2" readonly>
</div>
<span v-else>{{subitem.tp2}}</span>
</td>
<td>
<div class="form-group" v-if="crt == si">
<label>Rabatt</label>
<input v-model="subitem.discount" #change="calculateSubitem();">
</div>
<span v-else>{{subitem.discount}}</span>
</td>
</tr>
`,
methods: {
calculateSubitem: function() {
if(this.subitem.hasOwnProperty('eprice') && !isNaN(this.subitem.eprice)) {
if(!this.subitem.hasOwnProperty('factor') || isNaN(this.subitem.factor))
this.subitem.factor = 1
if(!this.subitem.hasOwnProperty('discount') || isNaN(this.subitem.discount))
this.subitem.discount = 0
if(!this.subitem.hasOwnProperty('qty') || isNaN(this.subitem.qty))
this.subitem.qty = 0
let discount = 1 - (parseFloat(this.subitem.discount.toString().split(',').join('.')) / 100),
margin = 0
this.subitem.dprice = (this.subitem.eprice.split(',').join('.') * discount)
this.subitem.tp1 = (this.subitem.dprice * this.subitem.qty * this.subitem.factor)
this.subitem.margin = (this.subitem.tp1 * (parseFloat(margin) / 100))
this.subitem.tp2 = (this.subitem.tp1 + this.subitem.margin)
this.$forceUpdate()//{TODO} - find an alternative to $forceUpdate
}
},
parseExpresion: function() {
this.subitem.qty = parseFloat(this.subitem.qtytext.split(',').join('.')) || 0
this.$nextTick(function () {
this.calculateSubitem()
})
}
},
})
Vue.component('subitem-row-sum', {
props: ['sisum'],
template: `
<tr>
<td colspan="3">SUM</td>
<td>
<span>{{sisum.tp1}}</span>
</td>
<td>
<span>{{sisum.margin}}</span>
</td>
<td>
<span>{{sisum.tp2}}</span>
</td>
<td></td>
</tr>
`,
})
Vue.component('html-textarea',{
template: `<div class="html-textarea" contenteditable="true" #input="updateHTML" rows="3"></div>`,
props: ['value'],
mounted: function () {
this.$el.innerHTML = this.value;
},
methods: {
updateHTML: function(e) {
this.$emit('input', e.target.innerHTML)
}
}
})
const app = new Vue({
el: '#app',
data: {
obj: s, // main object for loading
ii: 0, // items index
si: 0, // subitems index
},
computed: {
items: function() {
return this.obj
},
row: function() {
if(!this.items.length)
this.items.push({})
return this.items[this.ii]
},
subitems: function() {
if(!this.row.hasOwnProperty('subitems'))
this.row.subitems = [{}]
return this.row.subitems
},
srow: function() {
if(!this.subitems.length)
this.subitems.push({})
return this.subitems[this.si]
},
sisum: function() {
let sisum = {
tp1: 0,
tp2: 0,
margin: 0
}
this.subitems.forEach(si => {
sisum.tp1 += si.tp1 || 0
sisum.tp2 += si.tp2 || 0
sisum.margin += si.margin || 0
})
return sisum
}
},
methods: {
setIi: function(i) {
this.ii = i
},
setSi: function(i) {
this.si = i
},
addItemRow: function() {
this.items.push({})
this.setIi(this.items.length - 1)
this.$nextTick(function () {
document.getElementById('items').scrollIntoView({behavior: "smooth", block: "end", inline: "nearest"})
})
},
addSubitemRow: function() {
this.subitems.push({})
this.setSi(this.subitems.length - 1)
this.$nextTick(function () {
document.getElementById('subitems').scrollIntoView({behavior: "smooth", block: "end", inline: "nearest"})
})
},
},
})
body {
padding: 0;
margin: 0;
font-size: 14px;
font-family: 'Courier New', monotype;
}
.tables-container {
display: flex;
flex-direction: column;
height: 100vh;
}
.table-container {
height: calc(50% - 2px);
overflow: auto;
margin: 0 0 30px 0;
background: #eee
}
.table-container td {
padding: 3px 6px;
width: 60px;
height: 30px;
border-bottom: 1px solid black;
vertical-align: middle;
font-size: 1.1rem;
}
.html-textarea-container {
position: relative;
}
.html-textarea {
background: white;
border: 1px solid black;
}
.btn-group-edit {
position: absolute;
right: 2px;
bottom: 2px;
/*display: none;*/
z-index: 1;
}
/*.html-textarea:hover + .btn-group-edit,
.html-textarea:focus + .btn-group-edit {
display: block;
}*/
.table-container .form-group > input,
.table-container .form-group > textarea,
.table-container .form-group > select,
.html-textarea {
width: calc(200px - 12px);
height: calc(30px - 6px);
font-size: 1.1rem;
font-family: 'Courier New', monotype;
word-wrap: break-word;
}
.table-container .form-group > textarea,
.html-textarea {
width: calc(600px - 12px);
}
/*.table-container .form-group > textarea:focus,
.html-textarea {
height: calc(150px - 6px);
}*/
.btn-add {
width: 30px;
height: 30px;
padding: 4px 0;
}
<div id="app">
<div class="tables-container">
<div class="table-container table-subitems">
<table id="subitems">
<tr is="subitem-row" v-for="(subitem, i) in subitems" v-bind:subitem="subitem" v-bind:key="i" v-bind:crt="i" v-bind:si="si" v-on:click.native="setSi(i)"></tr>
<tr is="subitem-row-sum" v-bind:sisum="sisum"></tr>
</table>
</div>
<button class="btn-add" v-on:click="addSubitemRow">+</button>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
Adding numbers to "EPE" and "Anzahl" fields the others are automatically filled, but the sums not. What is my mistake here? Being a computed property, I was thinking that it will be reactive and update itself, based on the current set of subitems.
Thank you.
Welcome to Stack Overflow. I've made an attempt at fixing your code, tbh I found it really difficult to understand and I think you're overusing computed properties, e.g.
items: function() {
return this.obj
},
You might as well just reference this.obj instead of this.items as you're over complicating your code.
Your main problem was initialising the data, Since Vue doesn’t allow dynamically adding root-level reactive properties, you have to initialize Vue instances by declaring all root-level reactive data properties upfront, even with an empty value Declaring Reactive Properties
So I initialised s like this:
let s = [{
subitems: [{
eprice: "0",
factor: 0,
discount: 0,
qty: 0,
dprice: 0,
tp1: 0,
margin: 0,
tp2: 0
}]
}];
let s = [{
subitems: [{
eprice: "0",
factor: 0,
discount: 0,
qty: 0,
dprice: 0,
tp1: 0,
margin: 0,
tp2: 0
}]
}];
Vue.component('subitem-row', {
props: ['subitem', 'crt', 'si'],
template: `
<tr>
<td>
<div class="form-group" v-if="crt == si">
<label>EPE</label>
<input v-model="subitem.eprice" #change="calculateSubitem();">
</div>
<span v-else>{{subitem.eprice}}</span>
</td>
<td>
<div class="form-group" v-if="crt == si" #change="parseExpresion(); calculateSubitem();">
<label>Anzahl</label>
<input v-model="subitem.qtytext">
</div>
<span v-else>{{subitem.qtytext}}</span>
</td>
<td>
<div class="form-group" v-if="crt == si">
<label>Faktor</label>
<input v-model="subitem.factor" #change="calculateSubitem();">
</div>
<span v-else>{{subitem.factor}}</span>
</td>
<td>
<div class="form-group" v-if="crt == si">
<label>EK</label>
<input v-model="subitem.tp1" readonly>
</div>
<span v-else>{{subitem.tp1}}</span>
</td>
<td>
<div class="form-group" v-if="crt == si">
<label>Marge</label>
<input v-model="subitem.margin" readonly>
</div>
<span v-else>{{subitem.margin}}</span>
</td>
<td>
<div class="form-group" v-if="crt == si">
<label>VK</label>
<input v-model="subitem.tp2" readonly>
</div>
<span v-else>{{subitem.tp2}}</span>
</td>
<td>
<div class="form-group" v-if="crt == si">
<label>Rabatt</label>
<input v-model="subitem.discount" #change="calculateSubitem();">
</div>
<span v-else>{{subitem.discount}}</span>
</td>
</tr>
`,
methods: {
calculateSubitem: function() {
if (this.subitem.hasOwnProperty('eprice') && !isNaN(this.subitem.eprice)) {
if (!this.subitem.hasOwnProperty('factor') || isNaN(this.subitem.factor))
this.subitem.factor = 1
if (!this.subitem.hasOwnProperty('discount') || isNaN(this.subitem.discount))
this.subitem.discount = 0
if (!this.subitem.hasOwnProperty('qty') || isNaN(this.subitem.qty))
this.subitem.qty = 0
let discount = 1 - (parseFloat(this.subitem.discount.toString().split(',').join('.')) / 100),
margin = 0
this.subitem.dprice = (this.subitem.eprice.split(',').join('.') * discount)
this.subitem.tp1 = (this.subitem.dprice * this.subitem.qty * this.subitem.factor)
this.subitem.margin = (this.subitem.tp1 * (parseFloat(margin) / 100))
this.subitem.tp2 = (this.subitem.tp1 + this.subitem.margin)
this.$forceUpdate() //{TODO} - find an alternative to $forceUpdate
}
},
parseExpresion: function() {
this.subitem.qty = parseFloat(this.subitem.qtytext.split(',').join('.')) || 0
this.$nextTick(function() {
this.calculateSubitem()
})
}
},
})
Vue.component('subitem-row-sum', {
props: ['sisum'],
template: `
<tr>
<td colspan="3">SUM</td>
<td>
<span>{{sisum.tp1}}</span>
</td>
<td>
<span>{{sisum.margin}}</span>
</td>
<td>
<span>{{sisum.tp2}}</span>
</td>
<td></td>
</tr>
`,
})
Vue.component('html-textarea', {
template: `<div class="html-textarea" contenteditable="true" #input="updateHTML" rows="3"></div>`,
props: ['value'],
mounted: function() {
this.$el.innerHTML = this.value;
},
methods: {
updateHTML: function(e) {
this.$emit('input', e.target.innerHTML)
}
}
})
const app = new Vue({
el: '#app',
data: {
obj: s, // main object for loading
ii: 0, // items index
si: 0, // subitems index
},
computed: {
items: function() {
return this.obj
},
row: function() {
if (!this.items.length)
this.items.push({})
return this.items[this.ii]
},
subitems: function() {
return this.row.subitems
},
srow: function() {
if (!this.subitems.length)
this.subitems.push({})
return this.subitems[this.si]
},
sisum: function() {
debugger;
let sisum = {
tp1: 0,
tp2: 0,
margin: 0
}
this.subitems.forEach(si => {
sisum.tp1 += si.tp1 || 0
sisum.tp2 += si.tp2 || 0
sisum.margin += si.margin || 0
})
return sisum;
}
},
methods: {
setIi: function(i) {
this.ii = i
},
setSi: function(i) {
this.si = i
},
addItemRow: function() {
this.items.push({})
this.setIi(this.items.length - 1)
this.$nextTick(function() {
document.getElementById('items').scrollIntoView({
behavior: "smooth",
block: "end",
inline: "nearest"
})
})
},
addSubitemRow: function() {
this.subitems.push({})
this.setSi(this.subitems.length - 1)
this.$nextTick(function() {
document.getElementById('subitems').scrollIntoView({
behavior: "smooth",
block: "end",
inline: "nearest"
})
})
},
},
})
body {
padding: 0;
margin: 0;
font-size: 14px;
font-family: 'Courier New', monotype;
}
.tables-container {
display: flex;
flex-direction: column;
height: 100vh;
}
.table-container {
height: calc(50% - 2px);
overflow: auto;
margin: 0 0 30px 0;
background: #eee
}
.table-container td {
padding: 3px 6px;
width: 60px;
height: 30px;
border-bottom: 1px solid black;
vertical-align: middle;
font-size: 1.1rem;
}
.html-textarea-container {
position: relative;
}
.html-textarea {
background: white;
border: 1px solid black;
}
.btn-group-edit {
position: absolute;
right: 2px;
bottom: 2px;
/*display: none;*/
z-index: 1;
}
/*.html-textarea:hover + .btn-group-edit,
.html-textarea:focus + .btn-group-edit {
display: block;
}*/
.table-container .form-group>input,
.table-container .form-group>textarea,
.table-container .form-group>select,
.html-textarea {
width: calc(200px - 12px);
height: calc(30px - 6px);
font-size: 1.1rem;
font-family: 'Courier New', monotype;
word-wrap: break-word;
}
.table-container .form-group>textarea,
.html-textarea {
width: calc(600px - 12px);
}
/*.table-container .form-group > textarea:focus,
.html-textarea {
height: calc(150px - 6px);
}*/
.btn-add {
width: 30px;
height: 30px;
padding: 4px 0;
}
<div id="app">
<div class="tables-container">
<div class="table-container table-subitems">
<table id="subitems">
<tr is="subitem-row" v-for="(subitem, i) in subitems" v-bind:subitem="subitem" v-bind:key="i" v-bind:crt="i" v-bind:si="si" v-on:click.native="setSi(i)"></tr>
<tr is="subitem-row-sum" v-bind:sisum="sisum"></tr>
</table>
</div>
<button class="btn-add" v-on:click="addSubitemRow">+</button>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
For every product's <img> element on the page, I want to create an associated JavaScript object with attributes of title, description and price. When the <img> is clicked, it should show a modal based on a HTML template with the fields populated by these attributes.
Here is my current code:
document.getElementById("red").onclick = function() {functionRed()};
document.getElementById("yellow").onclick = function() {functionYellow()};
document.getElementById("blue").onclick = function() {functionBlue()};
// Create an object:
var Blue = {Title:"BlueIsTheNewBlack", Price:"500", Description:"This blue is the best color"}
var Yellow = {Title:"YellowIsTheNewBlack", Price:"900", Description:"This yellow is the best color"}
var Red = {Title:"RedIsTheNewBlack", Price:"100", Description:"This red is the best color"}
function functionRed() {
document.getElementById('id01').style.display='block';
// Display some data from the object:
document.getElementById("TitleModal").innerHTML = Red.Title;
document.getElementById("PriceModal").innerHTML = Red.Price;
document.getElementById("DescriptionModal").innerHTML = Red.Description;
}
function functionYellow() {
document.getElementById('id01').style.display='block';
// Display some data from the object:
document.getElementById("TitleModal").innerHTML = Yellow.Title;
document.getElementById("PriceModal").innerHTML = Yellow.Price;
document.getElementById("DescriptionModal").innerHTML = Yellow.Description;
}
function functionBlue() {
document.getElementById('id01').style.display='block';
// Display some data from the object:
document.getElementById("TitleModal").innerHTML = Blue.Title;
document.getElementById("PriceModal").innerHTML = Blue.Price;
document.getElementById("DescriptionModal").innerHTML = Blue.Description;
}
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<div style="background-color:red; width:50px; height:50px; float: left;" id="red"></div>
<div style="background-color:blue; width:50px; height:50px; float: left;" id="blue"></div>
<div style="background-color:yellow; width:50px; height:50px; float: left;" id="yellow"></div>
<div id="id01" class="w3-modal">
<div class="w3-modal-content">
<div class="w3-container">
<span onclick="document.getElementById('id01').style.display='none'" class="w3-button w3-display-topright">×</span>
<h2 id="TitleModal">Some text. Some text. Some text.</h2>
<h2 id="PriceModal">Some text. Some text. Some text.</h2>
<p id="DescriptionModal">Some text. Some text. Some text.</h2>
</div>
</div>
</div>
</div>
Thanks to everybody.
Is that what you expect ?
const SquareColors
= document.getElementById('ColorsChoices')
, SquareMessage
= { onRed : { Title: 'RedIsTheNewBlack', Price: '100', Description: 'This red is the best color' }
, onBlue : { Title: 'BlueIsTheNewBlack', Price: '500', Description: 'This blue is the best color' }
, onYellow : { Title: 'YellowIsTheNewBlack', Price: '900', Description: 'This yellow is the best color' }
}
, Modal_info
= document.getElementById('idModal')
;
Modal_info.show=_=>Modal_info.classList.remove('noDisplay')
Modal_info.hide=_=>Modal_info.classList.add('noDisplay')
idModal.onclick=e=>
{
if (!e.target.matches('#close-button, #idModal')) return
e.preventDefault()
Modal_info.hide()
}
const TitleModal = document.getElementById('TitleModal')
, PriceModal = document.getElementById('PriceModal')
, DescriptionModal = document.getElementById('DescriptionModal')
;
SquareColors.onclick=e=>
{
if (!e.target.matches('.colorButton')) return
let inColor = e.target.id
TitleModal.textContent = SquareMessage[inColor].Title
PriceModal.textContent = SquareMessage[inColor].Price
DescriptionModal.textContent = SquareMessage[inColor].Description
Modal_info.show()
}
nav#ColorsChoices div {
width : 50px;
height: 50px;
float : left;
}
#onRed { background-color: red }
#onBlue { background-color: blue }
#onYellow { background-color: yellow }
.noDisplay { display: none }
#idModal {
z-index: 3;
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: rgba(0,0,0,0.4);
}
#idModal > div {
position: relative;
display: block;
width: 30em;
margin: 5em auto;
background-color: #fff;
padding: 1em;
}
#close-button {
position: absolute;
top: 0;
right: .3em;
cursor: pointer;
font-size: 2em;
font-weight: bold;
}
#close-button:hover { color: crimson; }
<nav id="ColorsChoices">
<div class="colorButton" id="onRed"></div>
<div class="colorButton" id="onBlue"></div>
<div class="colorButton" id="onYellow"></div>
</nav>
<div id="idModal" class="noDisplay">
<div>
<span id="close-button">×</span>
<h2 id="TitleModal">Some text. </h2>
<h2 id="PriceModal">Some text. </h2>
<p id="DescriptionModal">Some text. </h2>
</div>
</div>
i working on mixitup plugin with jRange slider jquery plugin and is working good without jRange slider, but when i include jrange slider for mobiles price its not filter and not show product within the range. how to filter multiple selector. for example when click on iphone products , its show all iphone product but i want to also show iphone products with in price range. or any other products.
how to fix the problem.
jQuery(document).ready(function($) {
var price_mega = {};
main_filter();
$('.slider-input').jRange({
from: 0,
to: 400,
format: '$%s',
showLabels: true,
isRange : true,
onstatechange: function(value){
result = value.split(',');
price_mega = result;
main_filter();
}
});
function main_filter(){
$('.container').each(function(index, el) {
var min_price = Number(price_mega[0]);
var max_price = Number(price_mega[1]);
// console.log(min_price);
var active = $(this).data('active');
var wrap = $(this).closest('.main-wrapper');
var target = wrap.find('.target_filter');
var filter = wrap.find('.controls .filter');
// var filter = wrap.find('.container').find('.target_filter').filter(function(){
// var price = Number($(this).attr('data-price'));
// return price >= min_price && price <= max_price
// });
wrap.find('.container').mixItUp({
selectors: {
target: target,
filter: filter
},
load: {
filter: active,
}
});
});
}
});
.controls {
padding: 1rem;
background: #333;
font-size: 0.1px;
}
.controls button{
font-size: 27px;
color: gray;
margin-left: 20px;
}
.mixitup-control-active {
background: #393939;
}
.mixitup-control-active[data-filter]:after {
background: transparent;
}
.mix,
.gap {
display: inline-block;
vertical-align: top;
}
.mix {
background: #fff;
border-top: .5rem solid currentColor;
border-radius: 2px;
margin-bottom: 1rem;
position: relative;
display: none;
}
.mix.green {
color: #91e6c7;
}
.mix.pink {
color: #d595aa;
}
.mix.blue {
color: #5ecdde;
}
.mix,
.gap {
width: calc(100%/2 - (((2 - 1) * 1rem) / 2));
}
#media screen and (min-width: 541px) {
.mix,
.gap {
width: calc(100%/3 - (((3 - 1) * 1rem) / 3));
}
}
#media screen and (min-width: 961px) {
.mix,
.gap {
width: calc(100%/4 - (((4 - 1) * 1rem) / 4));
}
}
#media screen and (min-width: 1281px) {
.mix,
.gap {
width: calc(100%/5 - (((5 - 1) * 1rem) / 5));
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mixitup/2.1.11/jquery.mixitup.js"></script>
<script src="http://nitinhayaran.github.io/jRange/jquery.range.js"></script>
<link href="http://nitinhayaran.github.io/jRange/jquery.range.css" rel="stylesheet"/>
<div class="main-wrapper">
<div class="controls">
<button type="button" data-filter="all" class="filter">All</button>
<button type="button" data-filter=".samsung" class="filter">Samsung</button>
<button type="button" data-filter=".iphone" class="filter">Iphone</button>
<button type="button" data-filter=".blackberry" class="filter">Blackberry</button>
<div style="margin-top: 5%;height: 22px;">
<input type="hidden" class="filter slider-input" value="0,400" />
</div>
</div>
<div class="container" data-active=".samsung">
<div class="target_filter mix samsung" data-price="200">samsung 1(price $200)</div>
<div class="target_filter mix blackberry" data-price="111">blackberry 1(price $111)</div>
<div class="target_filter mix samsung" data-price="165">samsung 2(price $165)</div>
<div class="target_filter mix iphone" data-price="300">iphone 1(price $300)</div>
<div class="target_filter mix iphone" data-price="340">iphone 2 (price $340)</div>
<div class="target_filter mix samsung" data-price="100">samsung 3 (price $100)</div>
<div class="target_filter mix blackberry" data-price="89">blackberry 2(price $89)</div>
<div class="target_filter mix iphone" data-price="232">iphone 3(price $232)</div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
</div>
</div>
i'm currently working through Udemy's Vue.js tutorial. I've reached the section where you are building a battle web app game. After finishing it, I decided to practice my refactoring and came across this bug.
When you click the attack buttons and then the confirm box comes up to ask if you want to play again, it seems to add one extra item in my log array instead of resetting the game fully.
I'm suspecting it is to do with pressing the attack buttons too quickly, and then the confirm box comes up before running an addToLog() and then it runs it afterwards.
Or it could be my bad code. lol
Note that I know that clicking cancel on the confirm box also comes up with bugs too.
index.html
<!DOCTYPE html>
<html>
<head>
<title>Monster Slayer</title>
<script src="https://npmcdn.com/vue/dist/vue.js"></script>
<link rel="stylesheet" href="css/foundation.min.css">
<link rel="stylesheet" href="css/app.css">
</head>
<body>
<div id="app">
<section class="row">
<div class="small-6 columns">
<h1 class="text-center">YOU</h1>
<div class="healthbar">
<div class="healthbar text-center" style="background-color: green; margin: 0; color: white;" :style="{width: playerHealth + '%'}">
{{ playerHealth }}
</div>
</div>
</div>
<div class="small-6 columns">
<h1 class="text-center">BADDY</h1>
<div class="healthbar">
<div class="healthbar text-center" style="background-color: green; margin: 0; color: white;" :style="{width: computerHealth + '%'}">
{{ computerHealth }}
</div>
</div>
</div>
</section>
<section class="row controls" v-if="!isRunning">
<div class="small-12 columns">
<button id="start-game" #click="startGame">START GAME</button>
</div>
</section>
<section class="row controls" v-else>
<div class="small-12 columns">
<button id="attack" #click="attack">ATTACK</button>
<button id="special-attack" #click="specialAttack">SPECIAL ATTACK</button>
<button id="heal" #click="heal">HEAL</button>
<button id="restart" #click="restart">RESTART</button>
</div>
</section>
<section class="row log" v-if="turns.length > 0">
<div class="small-12 columns">
<ul>
<li v-for="turn in turns" :class="{'player-turn': turn.isPlayer, 'monster-turn': !turn.isPlayer}">
{{ turn.text }}
</li>
</ul>
</div>
</section>
</div>
<script src="app.js"></script>
</body>
</html>
css/app.css
.text-center {
text-align: center;
}
.healthbar {
width: 80%;
height: 40px;
background-color: #eee;
margin: auto;
transition: width 500ms;
}
.controls,
.log {
margin-top: 30px;
text-align: center;
padding: 10px;
border: 1px solid #ccc;
box-shadow: 0px 3px 6px #ccc;
}
.turn {
margin-top: 20px;
margin-bottom: 20px;
font-weight: bold;
font-size: 22px;
}
.log ul {
list-style: none;
font-weight: bold;
text-transform: uppercase;
}
.log ul li {
margin: 5px;
}
.log ul .player-turn {
color: blue;
background-color: #e4e8ff;
}
.log ul .monster-turn {
color: red;
background-color: #ffc0c1;
}
button {
font-size: 20px;
background-color: #eee;
padding: 12px;
box-shadow: 0 1px 1px black;
margin: 10px;
}
#start-game {
background-color: #aaffb0;
}
#start-game:hover {
background-color: #76ff7e;
}
#attack {
background-color: #ff7367;
}
#attack:hover {
background-color: #ff3f43;
}
#special-attack {
background-color: #ffaf4f;
}
#special-attack:hover {
background-color: #ff9a2b;
}
#heal {
background-color: #aaffb0;
}
#heal:hover {
background-color: #76ff7e;
}
#restart {
background-color: #ffffff;
}
#restart:hover {
background-color: #c7c7c7;
}
app.js
new Vue({
el: app,
data: {
playerHealth: 100,
computerHealth: 100,
isRunning: false,
turns: [],
},
methods: {
startGame: function() {
this.isRunning = true;
this.playerHealth = 100;
this.computerHealth = 100;
this.clearLog();
},
attackController: function(attacker, maxRange, minRange) {
let receiver = this.setReceiver(attacker);
let damage = 0;
if (attacker === 'player') {
damage = this.randomDamage(maxRange, minRange);
this.computerHealth -= damage;
}
if (attacker === 'computer') {
damage = this.randomDamage(maxRange, minRange);
this.playerHealth -= damage;
}
this.addToLog(attacker, receiver, damage);
if (this.checkWin()) {
return;
}
},
attack: function() {
this.attackController('player', 10, 3);
this.attackController('computer', 10, 3);
},
specialAttack: function() {
this.attackController('player', 30, 5);
this.attackController('computer', 30, 5);
},
heal: function() {
if (this.playerHealth <= 90) {
this.playerHealth += 10;
} else {
this.playerHealth = 100;
}
this.turns.unshift({
isPlayer: true,
text: 'Player heals for ' + 10,
});
},
randomDamage: function(max, min) {
return Math.floor(Math.random() * max, min);
},
checkWin: function() {
if (this.computerHealth <= 0) {
this.alertBox('YOU WIN! New Game?');
} else if (this.playerHealth <= 0) {
this.alertBox('LOSER!!! New Game?');
}
return false;
},
alertBox: function(message) {
if (confirm(message)) {
this.isRunning = false;
this.startGame();
} else {
this.isRunning = false;
}
return true;
},
restart: function() {
this.isRunning = false;
this.startGame();
},
addToLog: function(attacker, receiver, damage) {
this.turns.unshift({
isPlayer: attacker === 'player',
text: attacker + ' hits ' + receiver + ' for ' + damage,
});
},
clearLog: function() {
this.turns = [];
},
setReceiver: function(attacker) {
if (attacker === 'player') {
return 'computer';
} else {
return 'player';
}
},
damageOutput: function(attacker, health) {
if (attacker === 'player') {
damage = this.randomDamage(maxRange, minRange);
this.computerHealth -= damage;
}
},
},
});
Github repo is here if you prefer that. Thanks!
Your attack (and specialAttack) function attacks for both players:
attack: function() {
this.attackController('player', 10, 3);
this.attackController('computer', 10, 3);
},
Currently, it is checking for win at every attackController call. So when the first attacker (player) wins, the game resets AND the second player attacks.
So, my suggestion, move the checkWin out of the attackController into the attack functions:
attack: function() {
this.attackController('player', 10, 3);
this.attackController('computer', 10, 3);
this.checkWin();
},
The same to specialAttack.
Code/JSFiddle: https://jsfiddle.net/acdcjunior/wwc1xnyc/10/
Note, when the player wins, in the code above, the computer will still "strike back", even though the game is over. If you want to halt that, make checkWin return if the game is over:
checkWin: function() {
if (this.computerHealth <= 0) {
this.alertBox('YOU WIN! New Game?');
return true;
} else if (this.playerHealth <= 0) {
this.alertBox('LOSER!!! New Game?');
return true;
}
return false;
},
And add an if to attack (and specialAttack):
attack: function() {
this.attackController('player', 10, 3);
if (this.checkWin()) return;
this.attackController('computer', 10, 3);
this.checkWin();
},
Updated fiddle: https://jsfiddle.net/acdcjunior/wwc1xnyc/13/
I need to enable Virtual keyboard in Odoo and I made it almost. But the thing is when I click to a particular letter it comes on all the fields like below.
I have to resolve this.The entire code is just below including xml,javascript.
hotel.xml
<?xml version="1.0" encoding="utf-8"?>
<template xml:space="preserve">
<t t-name="HotelRegistrationMenu">
<div class="o_hr_attendance_kiosk_mode_container">
<div class="o_hr_attendance_kiosk_mode">
<h1>Welcome to Hotel California</h1>
<h2>We serve for your happiness</h2>
<button class="o_bi_hotel_button_registration btn btn-primary btn-sm">Book Now</button>
</div>
</div>
</t>
<t t-name="HotelRegistrationKeys">
<div class="o_hr_attendance_kiosk_mode_container">
<form>
<div class="o_form_view">
<table class="o_border" >
<tr>
<td class="col-xs-6 col-xs-offset-2"><label>Customer Name</label></td>
<td width="100"><input class="o_hr_attendance_PINbox" name="customer_name" minlength="1" autofocus="autofocus"/></td>
</tr>
<tr>
<td class="col-xs-6 col-xs-offset-2"><label>Phone Number</label></td>
<td width="100%"><input class="o_hr_attendance_PINbox" name="contact_number" minlength="1"/></td>
</tr>
<tr>
<td class="col-xs-6 col-xs-offset-2"><label>Number Of Persons</label></td>
<td width="100%"><input class="o_hr_attendance_PINbox" name="total_persons" minlength="1"/></td>
</tr>
<tr>
<td class="col-xs-6 col-xs-offset-2"><label>Required Date</label></td>
<td width="100%"><input class="o_hr_attendance_PINbox" name="required_date" minlength="1"/></td>
</tr>
<tr>
<td class="col-xs-6 col-xs-offset-2"><label>Required Time</label></td>
<td width="100%"><input class="o_hr_attendance_PINbox" name="required_time" minlength="1"/></td>
</tr>
</table>
</div>
</form>
<!-- <div class="o_hr_attendance_kiosk_mode"> -->
<div class="row">
<div class="align=center col-sm-8 col-sm-offset-2">
<div class="row" >
<div class="col-xs-4 col-xs-offset-2"></div>
</div>
<div class="row o_hr_attendance_pin_pad">
<t t-foreach="['A','B','C','D','E','F','G','H','I','J','K','L','M',
'N','O','P','Q','R','S','T','U','V','W','X','Y','Z','1','2',
'3','4','5','6','7','8','9','0','-','OK','Del']" t-as="btn_name">
<div class="col-xs-1 o_hr_attendance_pin_pad_border">
<a t-attf-class="btn btn-primary btn-block btn-lg o_hr_attendance_btn-round-corners {{ 'o_hr_attendance_pin_pad_button_' + btn_name }}"><t t-esc="btn_name"/>
</a>
</div>
</t>
</div>
</div>
</div>
</div>
<!-- </div> -->
</t>
</template>
Javascript file
registration.js
odoo.define('bi_hotel.registration', function (require) {
"use strict";
// var BarcodeHandlerMixin = require('barcodes.BarcodeHandlerMixin');
var core = require('web.core');
var Widget = require('web.Widget');
var QWeb = core.qweb;
var _t = core._t;
var registration = Widget.extend({
template:'HotelRegistrationKeys',
events: {
"click .o_bi_hotel_button_registration": function(){
var self = this;
self.$el.html(QWeb.render('HotelRegistrationKeys', {widget: self}));},
'click .o_hr_attendance_pin_pad_button_A': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'A'); },
'click .o_hr_attendance_pin_pad_button_B': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'B'); },
'click .o_hr_attendance_pin_pad_button_C': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'C'); },
'click .o_hr_attendance_pin_pad_button_D': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'D'); },
'click .o_hr_attendance_pin_pad_button_E': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'E'); },
'click .o_hr_attendance_pin_pad_button_F': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'F'); },
'click .o_hr_attendance_pin_pad_button_G': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'G'); },
'click .o_hr_attendance_pin_pad_button_H': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'H'); },
'click .o_hr_attendance_pin_pad_button_I': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'I'); },
'click .o_hr_attendance_pin_pad_button_J': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'J'); },
'click .o_hr_attendance_pin_pad_button_K': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'K'); },
'click .o_hr_attendance_pin_pad_button_L': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'L'); },
'click .o_hr_attendance_pin_pad_button_M': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'M'); },
'click .o_hr_attendance_pin_pad_button_N': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'N'); },
'click .o_hr_attendance_pin_pad_button_O': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'O'); },
'click .o_hr_attendance_pin_pad_button_P': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'P'); },
'click .o_hr_attendance_pin_pad_button_Q': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'Q'); },
'click .o_hr_attendance_pin_pad_button_R': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'R'); },
'click .o_hr_attendance_pin_pad_button_S': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'S'); },
'click .o_hr_attendance_pin_pad_button_T': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'T'); },
'click .o_hr_attendance_pin_pad_button_U': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'U'); },
'click .o_hr_attendance_pin_pad_button_V': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'V'); },
'click .o_hr_attendance_pin_pad_button_W': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'W'); },
'click .o_hr_attendance_pin_pad_button_X': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'X'); },
'click .o_hr_attendance_pin_pad_button_Y': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'Y'); },
'click .o_hr_attendance_pin_pad_button_Z': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 'Z'); },
'click .o_hr_attendance_pin_pad_button_-': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + ' '); },
'click .o_hr_attendance_pin_pad_button_Del': function() { this.$('.o_hr_attendance_PINbox').val(''); },
'click .o_hr_attendance_pin_pad_button_0': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 0); },
'click .o_hr_attendance_pin_pad_button_1': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 1); },
'click .o_hr_attendance_pin_pad_button_2': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 2); },
'click .o_hr_attendance_pin_pad_button_3': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 3); },
'click .o_hr_attendance_pin_pad_button_4': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 4); },
'click .o_hr_attendance_pin_pad_button_5': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 5); },
'click .o_hr_attendance_pin_pad_button_6': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 6); },
'click .o_hr_attendance_pin_pad_button_7': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 7); },
'click .o_hr_attendance_pin_pad_button_8': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 8); },
'click .o_hr_attendance_pin_pad_button_9': function() { this.$('.o_hr_attendance_PINbox').val(this.$('.o_hr_attendance_PINbox').val() + 9); },
},
start: function (){
var self = this;
self.$el.html(QWeb.render('HotelRegistrationMenu', {widget: self}));
},
});
core.action_registry.add('bi_hotel_registration', registration);
return registration;
});
.less file
hotel.less
.o_hr_attendance_btn-round-corners {
border-radius: 10%;
}
.o_hr_attendance_pin_pad_border {
margin: 10px 0px 10px 0px;
}
.o_hr_attendance_PINbox {
background: #AEFF00;
border: 1px solid #d5d5d5;
text-align: left;
margin: 5px 0px 5px 0px;
}
.o_hr_attendance_kiosk_mode_container {
background: black;
.o-flex-display();
.o-flex-flow(column, nowrap);
.o-justify-content(center);
.o-align-items(center);
#media (min-width: #screen-xs-max) {
background: url("../../../../web_enterprise/static/src/img/application-switcher-bg.jpg") no-repeat center center fixed;
background-size: cover;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
}
.o_hr_attendance_kiosk_mode {
width: 100%;
text-align: center;
position: relative;
background-color: #fff;
padding: 2em;
h1 {
margin: 0 0 2rem 0;
}
.message_demo_barcodes {
font-size: 0.9em;
margin: 0;
}
img {
overflow:hidden; // prevent margins colapsing with h1
margin-top: 3rem;
width: 200px;
}
p {
text-align: left;
margin: 3rem 0;
}
> button {
font-size: 1.2em;
margin-bottom: 2rem;
width: 100%;
}
> button:last-child {
margin-bottom: 0;
}
#media (min-width: #screen-xs-max) {
flex: 0 0 auto;
width: 550px;
border-radius: 10px;
background-color: rgba(255,255,255,0.8);
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.6);
font-size: 1.2em;
padding: 3em;
}
}
.o_border{
width: 500px;
height: 450;
text-align: left;
margin: 0 auto;
background : #ECECEC;
font-size: 1.5em;
margin-bottom: 50px;
}
.o_hr_attendance_PINbox_1 {
background: #AEFF00;
border: 1px solid #d5d5d5;
text-align: left;
margin: 5px 0px 5px 0px;
}
These above all are codes to enable the virtual keyboard but the issue is raised above.Can anybody please resolve it ?