I have a Vue Calendar that I'm working on. When a day is clicked, I would like to open a full-width box under the week of the selected day that displays details of the day (Think Google Images layout) I know how to pass data in Vue, but how do I add this details (component, view?) under the row of the current week? (See my CodePen
<div class="calendar">
<div class="header z-depth-2">
<a #click="lastMonth" class="waves-effect waves-light btn"><i class="material-icons left">chevron_left</i>Last Month</a>
<p>{{month}} {{year}}</p>
<a #click="nextMonth" class="waves-effect waves-light btn"><i class="material-icons right">chevron_right</i>Next Month</a>
</div>
<ul class="dates month">
<li class="dow" v-for="dow in days">{{dow}}</li>
<li v-for="blank in firstDayOfMonth" class="day"></li>
<li v-for="date in daysInMonth" #click="openday(date)"
class="day" :class="{'today': date == initialDate && month == initialMonth && year == initialYear}">
<span>{{date}}</span>
</li>
</ul>
</div>
JS
new Vue({
el: '.calendar',
data: {
today: moment(),
dateContext: moment(),
days: ['S', 'M', 'T', 'W', 'T', 'F', 'S']
},
methods:{
nextMonth: function () {
var t = this;
t.dateContext = moment(t.dateContext).add(1, 'month');
},
lastMonth: function () {
var t = this;
t.dateContext = moment(t.dateContext).subtract(1, 'month');
}
},
computed: {
year: function () {
var t = this;
return t.dateContext.format('YYYY');
},
month: function () {
var t = this;
return t.dateContext.format('MMMM');
},
daysInMonth: function () {
var t = this;
return t.dateContext.daysInMonth();
},
currentDate: function () {
var t = this;
return t.dateContext.get('date');
},
firstDayOfMonth: function () {
var t = this;
var firstDay = moment(t.dateContext).subtract((t.currentDate - 1), 'days');
return firstDay.weekday();
},
//Previous Code Above
initialDate: function () {
var t = this;
return t.today.get('date');
},
initialMonth: function () {
var t = this;
return t.today.format('MMMM');
},
initialYear: function () {
var t = this;
return t.today.format('YYYY');
}
}
})
Because you have no concept of a row (you just auto-wrap), you can't really insert a box that takes up a row. What I've done here is insert a div that is positioned absolutely, so that I can make it full width, but it overlays the rows below, rather than shifting them out of the way.
If you rework it to have weeks, you could insert a detail box between weeks.
The basic working, though, is that you set a data item for the currently selected date when you click on a date, and you clear that item to dismiss the detail box (a click in the detail box does it, here). The HTML is controlled by a v-if.
new Vue({
el: '.calendar',
data: {
today: moment(),
dateContext: moment(),
days: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
selectedDate: null
},
methods: {
nextMonth: function() {
var t = this;
t.dateContext = moment(t.dateContext).add(1, 'month');
},
lastMonth: function() {
var t = this;
t.dateContext = moment(t.dateContext).subtract(1, 'month');
},
openday(date) {
this.selectedDate = date;
},
dismiss() {
this.selectedDate = null;
}
},
computed: {
year: function() {
var t = this;
return t.dateContext.format('YYYY');
},
month: function() {
var t = this;
return t.dateContext.format('MMMM');
},
daysInMonth: function() {
var t = this;
return t.dateContext.daysInMonth();
},
currentDate: function() {
var t = this;
return t.dateContext.get('date');
},
firstDayOfMonth: function() {
var t = this;
var firstDay = moment(t.dateContext).subtract((t.currentDate - 1), 'days');
return firstDay.weekday();
},
//Previous Code Above
initialDate: function() {
var t = this;
return t.today.get('date');
},
initialMonth: function() {
var t = this;
return t.today.format('MMMM');
},
initialYear: function() {
var t = this;
return t.today.format('YYYY');
}
}
})
*,
*:before,
*:after {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
body {
font-size: 1.5em;
font-weight: 100;
color: rgba(255, 255, 255, 1);
background: #222222;
}
.calendar {
transform: translate3d(0, 0, 0);
width: 100vw;
}
.header {
display: flex;
padding: 0 1em;
justify-content: space-between;
align-items: center;
width: 100%;
background-color: #333;
}
.fade-enter {
/*overflow: hidden;*/
opacity: 0;
}
.fade-enter-active {
animation: fadeIn 1s ease-out;
opacity: 1;
}
.month.in.next {
animation: moveFromTopFadeMonth .4s ease-out;
opacity: 1;
}
.month.out.next {
animation: moveToTopFadeMonth .4s ease-in;
opacity: 1;
}
.month.in.prev {
animation: moveFromBottomFadeMonth .4s ease-out;
opacity: 1;
}
.month.out.prev {
animation: moveToBottomFadeMonth .4s ease-in;
opacity: 1;
}
.dates {
display: flex;
flex-wrap: wrap;
}
.day {
width: 14%;
padding: 1em;
text-align: center;
cursor: pointer;
}
.dow {
width: 14%;
text-align: center;
padding: 1em;
color: teal;
font-weight: bold;
}
.detail-panel {
width: 100vw;
background-color: darkred;
color: white;
height: 10rem;
position: absolute;
left: 0;
}
.today {
color: teal;
font-weight: bold;
}
.day-name {
font-size: 1em;
text-transform: uppercase;
margin-bottom: 5px;
color: rgba(255, 255, 255, .5);
letter-spacing: .7px;
}
.day-number {
font-size: 24px;
letter-spacing: 1.5px;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.min.js"></script>
<link href="//cdnjs.cloudflare.com/ajax/libs/materialize/0.98.2/css/materialize.min.css" rel="stylesheet" />
<div class="calendar">
<div class="header z-depth-2">
<a #click="lastMonth" class="waves-effect waves-light btn"><i class="material-icons left">chevron_left</i>Last Month</a>
<p>{{month}} {{year}}</p>
<a #click="nextMonth" class="waves-effect waves-light btn"><i class="material-icons right">chevron_right</i>Next Month</a>
</div>
<ul class="dates month">
<li class="dow" v-for="dow in days">{{dow}}</li>
<li v-for="blank in firstDayOfMonth" class="day"></li>
<li v-for="date in daysInMonth" class="day" :class="{'today': date == initialDate && month == initialMonth && year == initialYear}">
<span #click="openday(date)">{{date}}</span>
<div class="detail-panel" v-if="selectedDate === date" #click="dismiss">Hi there I see you selected {{date}}</div>
</li>
</ul>
</div>
Related
I have a todo project with localStorage and SortableJS. I am having problem where when I sort my todo list, it won't update the localStorage. Can somebody help me figure out a way to save the sorted list? The code is below but would be nice to visit the codepen link under the snippet.
const clear = document.querySelector(".clear");
const dateElement = document.getElementById("date");
const list = document.getElementById("list");
const input = document.getElementById("input");
// Class names
const CHECK = "fa-check-circle";
const UNCHECK = "fa-circle-thin";
const LINE_THROUGH = "lineThrough";
// Variables
let LIST, id;
// Get item from localStorage
let data = localStorage.getItem("TODO");
// Check if data is not empty
if (data) {
LIST = JSON.parse(data);
id = LIST.length;
loadList(LIST);
} else {
LIST = [];
id = 0;
}
// Load items to the user's interface
function loadList(array) {
array.forEach(function(item) {
addToDo(item.name, item.id, item.done, item.trash);
});
}
// Clear the localStorage
clear.addEventListener("click", function() {
localStorage.clear();
location.reload();
})
// Show today's date
const options = {
weekday: "long",
month: "short",
day: "numeric"
};
const today = new Date();
dateElement.innerHTML = today.toLocaleDateString("en-US", options);
// Add to do function
function addToDo(toDo, id, done, trash) {
if (trash) {
return;
}
const DONE = done ? CHECK : UNCHECK;
const LINE = done ? LINE_THROUGH : "";
const item = `<li class="item">
<i class="fa ${DONE}" job="complete" id="${id}"></i>
<p class="text ${LINE}">${toDo}</p>
<i class="fa fa-trash-o de" job="delete" id="${id}"></i>
</li>
`;
const position = "beforeend";
list.insertAdjacentHTML(position, item);
}
// Add an item to the list when the user cick the enter key
document.addEventListener("keyup", function(event) {
if (event.keyCode == 13) {
const toDo = input.value;
// If the input isn't empty
if (toDo) {
addToDo(toDo);
LIST.push({
name: toDo,
id: id,
done: false,
trash: false
});
// Add item to localStorage
localStorage.setItem("TODO", JSON.stringify(LIST));
id++;
}
input.value = ""
}
});
// complete to do
function completeToDo(element) {
element.classList.toggle(CHECK);
element.classList.toggle(UNCHECK);
element.parentNode.querySelector(".text").classList.toggle(LINE_THROUGH);
LIST[element.id].done = LIST[element.id].done ? false : true;
}
// Remove to do
function removeToDo(element) {
element.parentNode.parentNode.removeChild(element.parentNode);
LIST[element.id].trash = true;
// Add item to localStorage
localStorage.setItem("TODO", JSON.stringify(LIST));
}
// Target the items created dynamically
list.addEventListener("click", function(event) {
const element = event.target;
const elementJob = element.attributes.job.value;
if (elementJob == "complete") {
completeToDo(element);
} else if (elementJob == "delete") {
removeToDo(element);
}
// Add item to localStorage
localStorage.setItem("TODO", JSON.stringify(LIST));
});
// For sorting the list
Sortable.create(list, {
animation: 100,
group: 'list-1',
draggable: '#list li',
handle: '#list li',
sort: true,
filter: '.sortable-disabled',
chosenClass: 'active'
});
/* ------------ youtube.com/CodeExplained ------------ */
body {
padding: 0;
margin: 0;
background-color: rgba(0, 0, 0, 0.1);
font-family: 'Titillium Web', sans-serif;
}
/* ------------ container ------------ */
.container {
padding: 10px;
width: 380px;
margin: 0 auto;
}
/* ------------ header ------------ */
.header {
width: 380px;
height: 200px;
background-image: url('');
background-size: 100% 200%;
background-repeat: no-repeat;
border-radius: 15px 15px 0 0;
position: relative;
}
.clear {
width: 30px;
height: 30px;
position: absolute;
right: 20px;
top: 20px;
}
.clear i {
font-size: 30px;
color: #FFF;
}
.clear i:hover {
cursor: pointer;
text-shadow: 1px 3px 5px #000;
transform: rotate(45deg);
}
#date {
position: absolute;
bottom: 10px;
left: 10px;
color: #FFF;
font-size: 25px;
font-family: 'Titillium Web', sans-serif;
}
/* ------------ content ------------ */
.content {
width: 380px;
height: 350px;
max-height: 350px;
background-color: #FFF;
overflow: auto;
}
.content::-webkit-scrollbar {
display: none;
}
.content ul {
padding: 0;
margin: 0;
}
.item {
width: 380px;
height: 45px;
min-height: 45px;
position: relative;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
list-style: none;
padding: 0;
margin: 0;
}
.item i.co {
position: absolute;
font-size: 25px;
padding-left: 5px;
left: 15px;
top: 10px;
}
.item i.co:hover {
cursor: pointer;
}
.fa-check-circle {
color: #6eb200;
}
.item p.text {
position: absolute;
padding: 0;
margin: 0;
font-size: 20px;
left: 50px;
top: 5px;
background-color: #FFF;
max-width: 285px;
}
.lineThrough {
text-decoration: line-through;
color: #ccc;
}
.item i.de {
position: absolute;
font-size: 25px;
right: 15px;
top: 10px;
}
.item i.de:hover {
color: #af0000;
cursor: pointer;
}
/* ------------ add item ------------ */
.add-to-do {
position: relative;
width: 360px;
height: 40px;
background-color: #FFF;
padding: 10px;
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
.add-to-do i {
position: absolute;
font-size: 40px;
color: #4162f6;
}
.add-to-do input {
position: absolute;
left: 50px;
height: 35px;
width: 310px;
background-color: transparent;
border: none;
font-size: 20px;
padding-left: 10px;
}
.add-to-do input::-webkit-input-placeholder {
/* Chrome/Opera/Safari */
color: #4162f6;
font-family: 'Titillium Web', sans-serif;
font-size: 20px;
}
.add-to-do input::-moz-placeholder {
/* Firefox 19+ */
color: #4162f6;
font-family: 'Titillium Web', sans-serif;
font-size: 20px;
}
.add-to-do input:-ms-input-placeholder {
/* IE 10+ */
color: #4162f6;
font-family: 'Titillium Web', sans-serif;
font-size: 20px;
}
.add-to-do input:-moz-placeholder {
/* Firefox 18- */
color: #4162f6;
font-family: 'Titillium Web', sans-serif;
font-size: 20px;
}
<script src="https://kit.fontawesome.com/ed2e310181.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/gh/RubaXa/Sortable/Sortable.min.js"></script>
<div class="container">
<div class="header">
<div class="clear">
<i class="fa fa-refresh"></i>
</div>
<div id="date"></div>
</div>
<div class="content">
<ul id="list">
<!-- <li class="item">
<i class="fa fa-circle-thin co" job="complete" id="0"></i>
<p class="text"></p>
<i class="fa fa-trash-o" job="delete" id="1"></i>
</li> -->
</ul>
</div>
<div class="add-to-do">
<i class="fa fa-plus-circle"></i>
<input type="text" id="input" placeholder="Add a to-do">
</div>
</div>
Please visit my codepen for a working project.
Try to add 2 or more todos then sort, on refresh was hoping to keep the sorted list.
https://codepen.io/Foxseiz/pen/ZEGadWZ
Sortable.create(list, {
group: "TODO2",
options: {
animation: 100,
draggable: "#list li",
handle: "#list li",
sort: true,
filter: ".sortable-disabled",
chosenClass: "active"
},
store: {
/**
* Get the order of elements. Called once during initialization.
* #param {Sortable} sortable
* #returns {Array}
*/
get: function(sortable) {
var order = localStorage.getItem(sortable.options.group.name);
return order ? order.split("|") : [];
},
/**
* Save the order of elements. Called onEnd (when the item is dropped).
* #param {Sortable} sortable
*/
set: function(sortable) {
var order = sortable.toArray();
localStorage.setItem(sortable.options.group.name, order.join("|"));
}
}
});
That would work in your case
For your Sortable.create option you can do the following:
// For sorting the list
Sortable.create(list, {
animation: 100,
group: 'list-1',
draggable: '#list li',
handle: '#list li',
sort: true,
filter: '.sortable-disabled',
chosenClass: 'active',
onSort: function(e) {
var items = e.to.children;
var result = [];
for (var i = 0; i < items.length; i++) {
result.push(items[i].id);
}
var lsBefore = JSON.parse(localStorage.getItem("TODO"));
var lsAfter = [];
for (var i = 0; i < result.length; i++) {
var found = false;
for (var j = 0; j < lsBefore.length && !found; j++) {
if (lsBefore[j].id == result[i]) {
lsAfter.push(lsBefore[j]);
lsBefore.splice(j, 1);
found = true;
}
}
}
localStorage.setItem("TODO", JSON.stringify(lsAfter));
console.log(result);
console.log(lsBefore);
console.log(lsAfter);
}
The lsAfter is your re-sorted set of objects that you can store/update in local storage.
My solution also requires that your const item looks like this (I added the id attribute to the <li> element:
const item = `<li class="item" id="${id}">
<i class="fa ${DONE}" job="complete" id="${id}"></i>
<p class="text ${LINE}">${toDo}</p>
<i class="fa fa-trash-o de" job="delete" id="${id}"></i>
</li>
`;
You need use onSort callback.
example code:
const clear = document.querySelector(".clear");
const dateElement = document.getElementById("date");
const list = document.getElementById("list");
const input = document.getElementById("input");
// Class names
const CHECK = "fa-check-circle";
const UNCHECK = "fa-circle-thin";
const LINE_THROUGH = "lineThrough";
// Variables
let LIST, id;
// Get item from localStorage
let data = localStorage.getItem("TODO");
// Check if data is not empty
if(data) {
LIST = JSON.parse(data);
id = LIST.length;
loadList(LIST);
}else{
LIST =[];
id = 0;
}
// Load items to the user's interface
function loadList(array) {
array.forEach(function(item){
addToDo(item.name, item.id, item.done, item.trash);
});
}
// Clear the localStorage
clear.addEventListener("click", function() {
localStorage.clear();
location.reload();
})
// Show today's date
const options = {weekday : "long", month : "short", day : "numeric"};
const today = new Date();
dateElement.innerHTML = today.toLocaleDateString("en-US", options);
// Add to do function
function addToDo(toDo, id, done, trash) {
if(trash) { return; }
const DONE = done ? CHECK : UNCHECK;
const LINE = done ? LINE_THROUGH : "";
const item = `<li class="item">
<i class="fa ${DONE}" job="complete" id="${id}"></i>
<p class="text ${LINE}">${toDo}</p>
<i class="fa fa-trash-o de" job="delete" id="${id}"></i>
</li>
`;
const position = "beforeend";
list.insertAdjacentHTML(position, item);
}
// Add an item to the list when the user cick the enter key
document.addEventListener("keyup", function(event) {
if(event.keyCode == 13) {
const toDo = input.value;
// If the input isn't empty
if(toDo) {
addToDo(toDo);
LIST.push({
name : toDo,
id : id,
done : false,
trash : false
});
// Add item to localStorage
localStorage.setItem("TODO", JSON.stringify(LIST));
id++;
}
input.value = ""
}
});
// complete to do
function completeToDo(element) {
element.classList.toggle(CHECK);
element.classList.toggle(UNCHECK);
element.parentNode.querySelector(".text").classList.toggle(LINE_THROUGH);
LIST[element.id].done = LIST[element.id].done ? false : true;
}
// Remove to do
function removeToDo(element) {
element.parentNode.parentNode.removeChild(element.parentNode);
LIST[element.id].trash = true;
// Add item to localStorage
localStorage.setItem("TODO", JSON.stringify(LIST));
}
// Target the items created dynamically
list.addEventListener("click", function(event) {
const element = event.target;
const elementJob = element.attributes.job.value;
if(elementJob == "complete") {
completeToDo(element);
}else if(elementJob == "delete"){
removeToDo(element);
}
// Add item to localStorage
localStorage.setItem("TODO", JSON.stringify(LIST));
});
function swapArrayElements(arr, indexA, indexB) {
var temp = arr[indexA];
arr[indexA] = arr[indexB];
arr[indexB] = temp;
};
function orderList(oldIndex, newIndex) {
swapArrayElements(LIST, oldIndex, newIndex)
localStorage.setItem("TODO", JSON.stringify(LIST));
}
// For sorting the list
Sortable.create(list, {
animation: 100,
group: 'list-1',
draggable: '#list li',
handle: '#list li',
sort: true,
filter: '.sortable-disabled',
chosenClass: 'active',
onSort: function (/**Event*/evt) {
orderList(evt.oldIndex, evt.newIndex);
},
});
When you call
Sortable.create(list, {
animation: 100,
group: 'list-1',
draggable: '#list li',
handle: '#list li',
sort: true,
filter: '.sortable-disabled',
chosenClass: 'active'
});
There is actually a store option you can add. Like this:
Sortable.create(list, {
store: {
//Get the order of elements. Called once during initialization.
// #param {Sortable} sortable
// #returns {Array}
get: function (sortable) {
var order = localStorage.getItem(sortable.options.group.name);
return order ? order.split('|') : [];
},
// Save the order of elements.
// #param {Sortable} sortable
set: function (sortable) {
var order = sortable.toArray();
localStorage.setItem(sortable.options.group.name, order.join('|'));
}
},
...rest of your options
});
Also Sortable.create returns a "Sortable" object for your list so building on the code you have above you now have access to the Sortable object
var mySortable = Sortable.create(list, {...your options});
Now you can call mySortable.Save() after any event and your store's set function will get called. For example put mysortable.Save() in your document.addEventListener("keyup") function
I attempt to create a simple Gantt chart (Table):
I use computed property newTasks() to imitate the getting asynchronously a list of tasks from a Vuex Store but my solution doesn’t work.
Could you help me please.
Not working solution with computed property: https://jsfiddle.net/AlexPilugin/98b0pw24/
new Vue({
el: "#app",
data: {
longMode: false,
columns: 7,
tasks: null
},
watch: {
newTasks(val) {
this.tasks = val;
this.$forceUpdate();
}
},
created() {
this.$nextTick(() => {
this.tasks = this.newTasks;
this.$forceUpdate();
})
},
computed: {
newTasks() {
let tasks = null;
console.log("newTasks-------------");
const self = this;
setTimeout(() => {
if (self.longMode) {
tasks = [{
start: 0,
end: 3
},
{
start: 2,
end: 9
},
{
start: 7,
end: 13
}
];
} else {
tasks = [{
start: 0,
end: 4
},
{
start: 2,
end: 6
}
];
}
console.log("return new tasks:");
console.log(tasks);
return tasks;
}, 2000);
}
},
methods: {
toggle() {
this.longMode = !this.longMode;
this.columns = this.longMode ? 14 : 7;
this.tasks = this.newTasks;
console.log("clicked! longMode: " + this.longMode);
},
getLineStyle(index) {
let rowIndex = 2;
console.log("getLineStyle(" + rowIndex + ")");
console.log(this.$refs);
if (this.tasks && this.tasks.length > 0) {
console.log(this.tasks);
let task = this.tasks[rowIndex];
console.log(task);
let start = task.start;
let end = task.end;
let startRef = 'cell' + start;
let endRef = 'cell' + end;
let startLeft = this.$refs[startRef][0].offsetLeft;
let endLeft = this.$refs[endRef][0].offsetLeft;
let taskWidth = (endLeft - startLeft) + 'px';
let styleObject = {
left: startLeft,
width: taskWidth
};
return styleObject;
}
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
table {
border-collapse: collapse;
table-layout: auto;
/*fixed;*/
border: 1px solid #012656;
}
.cell {
padding: 5px;
border: 1px solid #012656;
cursor: pointer;
}
td.cell:nth-child(7n-1),
td.cell:nth-child(7n) {
background: #eee;
}
.line {
position: absolute;
top: 4px;
height: 6px;
border-radius: 3px;
background: red;
overflow: hidden;
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<table class="gantt-table">
<tr #click="toggle">
<td v-for="(cell, colIndex) in columns" :ref="'cell'+colIndex" :key="'col-'+colIndex" class="cell">
{{ colIndex+1 }}
</td>
</tr>
<tr v-show="tasks && tasks.length > 0" v-for="(row, rowIndex) in tasks" :key="'tr-'+rowIndex">
<div style="position: relative;">
<div class="line" :style="getLineStyle(rowIndex)"></div>
</div>
<td v-for="(cell, colIndex) in columns" :key="'row-'+rowIndex+'col-'+colIndex" class="cell">
<div>  </div>
</td>
</tr>
</table>
</div>
Another approach (with method): https://jsfiddle.net/AlexPilugin/qxbd4gcw/
https://jsfiddle.net/AlexPilugin/98b0pw24/3/ - Script is not drawing extra rows based on number of tasks (tasks.length)
and rowIndex in getLineStyle(rowIndex) in undefined! Why?!!
Edit
With Promise
Why watch doesn't work?.
Why both solutions don't work:
<tr v-for="(row, rowIndex) in newTasks" ...>
<tr v-for="(row, rowIndex) in tasks" ..>
Vue.config.productionTip = false;
Vue.config.devtools=false;
new Vue({
el: "#app",
data: {
longMode: false,
columns: 7,
tasks: []
},
created() {
this.tasks = this.newTasks;
},
watch: {
newTasks(tasks) {
console.log("watch: newTasks()-------------");
console.log(tasks);
this.tasks = tasks;
this.$forceUpdate();
}
},
computed: {
newTasks() {
let tasks = null;
if(this.longMode) {
tasks = [
{start: 0, end: 3},
{start: 2, end: 9},
{start: 7, end: 13}
];
} else {
tasks = [
{start: 0, end: 4},
{start: 2, end: 6}
];
}
this.delay(2000).then(() => {
console.log("return new tasks:");
console.log(tasks);
return tasks;
})
}
},
methods: {
toggle() {
this.longMode = !this.longMode;
this.columns = this.longMode ? 14 : 7;
this.tasks = this.newTasks;
console.log("clicked! longMode: " + this.longMode);
},
delay(t) {
return new Promise(function(resolve) {
setTimeout(resolve, t)
});
},
getLineStyle(index) {
let rowIndex = 2;
console.log("getLineStyle("+rowIndex+")");
console.log(this.$refs);
if(this.tasks && this.tasks.length>0) {
console.log(this.tasks);
let task = this.tasks[rowIndex];
console.log(task);
let start = task.start;
let end = task.end;
let startRef = 'cell'+start;
let endRef = 'cell'+end;
let startLeft = this.$refs[startRef][0].offsetLeft;
let endLeft = this.$refs[endRef][0].offsetLeft;
let taskWidth = (endLeft - startLeft) + 'px';
let styleObject = {left: startLeft, width: taskWidth};
return styleObject;
}
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
table {
border-collapse: collapse;
table-layout: auto;
/*fixed;*/
border: 1px solid #012656;
}
.cell {
padding: 5px;
border: 1px solid #012656;
cursor: pointer;
}
td.cell:nth-child(7n-1),
td.cell:nth-child(7n) {
background: #eee;
}
.line {
position: absolute;
top: 4px;
height: 6px;
border-radius: 3px;
background: red;
overflow: hidden;
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<table class="gantt-table">
<tr #click="toggle">
<td
v-for="(cell, colIndex) in columns"
:ref="'cell'+colIndex"
:key="'col-'+colIndex"
class="cell"
>
{{ colIndex+1 }}
</td>
</tr>
<tr
v-show="newTasks && newTasks.length > 0"
v-for="(row, rowIndex) in newTasks"
:key="'tr-'+rowIndex"
>
<!--
<div style="position: relative;">
<div class="line" :style="getLineStyle(rowIndex)"></div>
</div>
-->
<td
v-for="(cell, colIndex) in columns"
:key="'row-'+rowIndex+'col-'+colIndex"
class="cell"
>
<div>  </div>
</td>
</tr>
</table>
</div>
I am new to Vue.js and I am stuck. I have pulled in API data from Github jobs via Vanilla JavaScript and now I want to create a user filter. I am starting out with a filter for job "type" meaning Full time, part time, etc. Also, this code in being put into a plugin in WordPress called Shortcoder. I have watched tutorials and looked at three other posts on Stackoverflow that are similar but cannot figure out why I am getting this error: "unexpected SyntaxError:Unexpected token: on the line that states selectedType="Full Time". Maybe I have just stared at it for too long and need a fresh set eyes. I would really appreciate someone else reviewing it. Thanks in advance. Here is my code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- VUE.JS CDN -->
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.22/dist/vue.js"></script>
<title>Github Jobs API</title>
<style>
* {
box-sizing: border-box
}
html {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-family: 'Dosis', sans-serif;
line-height: 1.6;
color: #696969;
background: white;
}
#root {
max-width: 1200px;
margin: 0 auto;
}
h1 {
text-align: center;
padding: 1.5rem 2.5rem;
background-color: #b0bac6;
margin: 0 0 2rem 0;
font-size: 1.5rem;
color: #696969;
}
p {
padding: 0 2.5rem 2.5rem;
margin: 0;
}
.container1{
display: flex;
flex-wrap: wrap;
}
.card {
margin: 1rem;
background: #bobac6;
box-shadow: 2px 4px 25px rgba(0, 0, 0, .1);
border-radius: 12px;
overflow: hidden;
transition: all .2s linear;
}
.card:hover {
box-shadow: 2px 8px 45px rgba(0, 0, 0, .15);
transform: translate3D(0, -2px, 0);
}
#media screen and (min-width: 600px) {
.card {
flex: 1 1 calc(50% - 2rem);
}
}
#media screen and (min-width: 900px) {
.card {
flex: 1 1 calc(33% - 2rem);
}
}
.card:nth-child(2n) h1 {
background-color: #b0bac6;
color: #696969;
}
.card:nth-child(4n) h1 {
background-color: #b0bac6;
color: #696969;
}
.card:nth-child(5n) h1 {
background-color #b0bac6;
color: #696969;
}
.container2 {
padding: 20px;
width: 90%;
max-width: 400px;
margin: 0 auto;
}
label {
display: block;
line-height: 1.5em;
}
ul {
margin-left: 0;
padding-left: 0;
list-style: none;
}
li {
padding: 8px 16px;
border-bottom: 1px solid #eee;
}
</style>
</head>
<body>
<!-- DIV for Vue.js filter -->
<div class="container2" id="jobs">
<div class="filter">
<label><input type="radio" v-model="selectedType" value="Full Time"/>Full Time</label>
<label><input type="radio" v-model="selectedType" value="Part Time"/>Part Time</label>
</div>
<ul class="job-list">
<li v-for="job in filteredJobs">{{ job.title }}</li>
</ul>
</div>
<!-- DIV FOR API DATA CARD DISPLAY -->
<div id="root"></div>
<script>
// USED STRICT MODE TO SOLVE: “UNCAUGHT SYNTAX ERROR: UNEXPECTED TOKEN
U IN JSON # POSITION 0”
'use strict';
// INITIATING APIDATA AS GLOBAL VARIABLE
var apiData= " ";
const app = document.getElementById('root');
const container1 = document.createElement('div');
container1.setAttribute('class', 'container1');
app.appendChild(container1);
var request = new XMLHttpRequest();
//GET REQUEST WITH USE OF HEROKU AS A PROXY TO SOLVE CORS ERROR
request.open('GET','https://cors-anywhere.herokuapp.com/https://jobs.github.com/positions.json?&markdown=true&page=1',
true);
request.onload = function () {
//CONVERT JSON DATA TO JAVASCRIPT OBJECTS USING JSON.PARSE
var apiData = JSON.parse(this.response);
if (request.status >= 200 && request.status < 400) {
apiData.forEach(job => {
const card = document.createElement('div');
card.setAttribute('class', 'card');
const h1 = document.createElement('h1');
h1.textContent = job.title;
const p = document.createElement('p');
job.description = job.description.substring(0, 300);
p.textContent = `${job.description}...`;
container1.appendChild(card);
card.appendChild(h1);
card.appendChild(p);
});
// ERROR HANDLING
} else {
const errorMessage = document.createElement('marquee');
errorMessage.textContent = `It's not working!`;
app.appendChild(errorMessage);
}
}
request.send();
// APPLY FILTER TO API DATA USING VUE.JS
var vm = new Vue({
el: "#jobs",
data() {
return {
apiData:[],
search: ' ',
filterJobs: [ ]},
selectedType:"Full Time"
},
computed: {
filteredJobs: function() {
vm = this;
var category = vm.selectedType;
if(type === "part time") {
return vm.jobs;
} else {
return vm.jobs.filter(function(job) {
return job.type === type;
});
}
}
}
});
</script>
</body>
</html>
The syntax errors are in data():
data() {
return {
apiData: [],
search: ' ',
filterJobs: []}, // <-- FIXME: unexpected '}'
selectedType: "Full Time"
// <-- FIXME: missing '}' for the return statement
},
computed: {
...
Here's the code corrected:
data() {
return {
apiData: [],
search: ' ',
filterJobs: [], // <-- fixed
selectedType: "Full Time"
} // <-- fixed
},
computed: {
...
var vm = new Vue({
el: "#jobs",
data() {
return {
apiData: [],
search: " ",
jobs: [],
selectedType: "full time"
};
},
computed: {
filteredJobs: function() {
vm = this;
var type = vm.selectedType;
if (type === "part time") {
return vm.jobs;
} else {
return vm.jobs.filter(function(job) {
return job.type === type;
});
}
}
},
mounted() {
this.jobs = [
{id: 1, type: 'part time'},
{id: 2, type: 'full time'},
{id: 3, type: 'part time'},
{id: 4, type: 'full time'},
{id: 5, type: 'part time'},
]
}
});
<script src="https://unpkg.com/vue#2.6.1/dist/vue.min.js"></script>
<div id="jobs">
<ul>
<li v-for="job in filteredJobs" :key="job.id">
type: {{job.type}}
</li>
</ul>
</div>
The problem I encountered is I can't get any results from the jQuery UI Autocomplete form because of the variable scope. Let me show you.
// TAKE A CLOSE LOOK AT THIS METHOD
select: function(e, ui) {
$('#instant-search').text(ui.item.label);
$("#search").autocomplete("option", "source",
function(request, response) {
getAutocompleteResults(function(d) {
// DOESN'T WORK response(d);
});
// WORKS BUT IT SHOULD BE A DYNAMIC ARRAY FROM THE "D" OBJECT
// response(["anarchism", "anarchist black cross", "black rose (symbolism)", "communist symbolism", "political symbolism"]);
});
$("#search").autocomplete("search", ui.item.label);
In order to return results I have to use a function response([...]); outside the getAutocompleteResults(function(d) { ... }); function.
However, the source should be dynamic and not like the static array. In other words:
The function response(d); should return an object, which contains a few properties (title, value, extract). I have to access them by using response(d);, however, this function doesn't work inside getAutocompleteResults(function(d) { ... }); function. How can I achieve this?
There is a small snippet of code, however, the main problem is the select method. You can find this in the middle of the whole code block. I commented it out.
$(function() {
$("html").removeClass("no-js");
var autocompleteResults = [{
title: [],
extract: [],
pageId: []
}];
var capitalizeFirstLetter = function(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
};
var changeText2 = function(e) {
var request = $("input").val() + String.fromCharCode(e.which);
$("#instant-search").text(request);
var getAutocompleteResults = function(callback) {
$.ajax({
url: "https://en.wikipedia.org/w/api.php?format=json&action=query&generator=search&gsrlimit=6&prop=extracts&origin=*&pilimit=max&exintro&explaintext&exsentences=1&gsrsearch=" +
$("#instant-search").text(),
beforeSend: function() {
$(".loading").show();
},
success: function(d) {
$(".loading").hide();
autocompleteResults[0].title = [];
autocompleteResults[0].extract = [];
autocompleteResults[0].pageId = [];
if (d.hasOwnProperty("query")) {
if (d.query.hasOwnProperty("pages")) {
$.each(d.query.pages, function(i) {
autocompleteResults[0].title.push(d.query.pages[i].title);
autocompleteResults[0].extract.push(d.query.pages[i].extract);
autocompleteResults[0].pageId.push(d.query.pages[i].pageid);
});
}
}
if (!autocompleteResults[0].length) {
$(".ui-autocomplete").hide();
}
autocompleteResults[0].title.sort(function(a, b) {
var nameA = a.toUpperCase();
var nameB = b.toUpperCase();
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
return 0;
});
autocompleteResults[0].title = autocompleteResults[0].title.map(
function(i) {
return i.toLowerCase();
}
);
callback(autocompleteResults[0]);
},
datatype: "json",
cache: false
});
};
$("#search").autocomplete({
source: function(request, response) {
getAutocompleteResults(function(d) {
var results = [],
filteredAutocompleteResults = [];
filteredAutocompleteResults = d.title.filter(function(i) {
return (
i !=
$("#instant-search")
.text()
.toLowerCase()
);
});
for (var i = 0; i < d.title.length; i++) {
results[i] = {
label: filteredAutocompleteResults[i],
extract: d.extract[i],
pageId: d.pageId[i]
};
}
if (results.length == 5) {
response(results);
} else {
response(results.slice(0, 5));
}
});
},
response: function() {
if ($("#instant-search").text()) {
$("table").css("display", "table");
$(".wikisearch-container").css("margin-top", 100);
}
},
close: function() {
if (!$(".ui-autocomplete").is(":visible")) {
$(".ui-autocomplete").show();
}
},
appendTo: ".input",
focus: function(e) {
e.preventDefault();
},
delay: 0,
// TAKE A CLOSE LOOK AT THIS METHOD
select: function(e, ui) {
$('#instant-search').text(ui.item.label);
$("#search").autocomplete("option", "source",
function(request, response) {
getAutocompleteResults(function(d) {
// DOESN'T WORK response(d);
});
// WORKS BUT IT SHOULD BE A DYNAMIC ARRAY FROM THE "D" OBJECT
// response(["anarchism", "anarchist black cross", "black rose (symbolism)", "communist symbolism", "political symbolism"]);
});
$("#search").autocomplete("search", ui.item.label);
// EVERYTHING SHOULD BE FINE BELOW THIS LINE
if ($(".search-results").css("opacity") != 1) {
$(".search-results h4").text(capitalizeFirstLetter(ui.item.label));
$(".search-results p").text(ui.item.extract);
$(".search-results a").prop(
"href",
"https://en.wikipedia.org/?curid=" + ui.item.pageId
);
$(".search-results").css("opacity", 1);
} else if (
$(".search-results h4")
.text()
.toLowerCase() != ui.item.label
) {
$(".search-results").css("opacity", 0);
setTimeout(function() {
$(".search-results h4").text(capitalizeFirstLetter(ui.item.label));
$(".search-results p").text(ui.item.extract);
$(".search-results a").prop(
"href",
"https://en.wikipedia.org/?curid=" + ui.item.pageId
);
$(".search-results").css("opacity", 1);
}, 500);
}
},
create: function() {
$(this).data("ui-autocomplete")._renderItem = function(ul, item) {
return $("<li>")
.append(
'<div class="ui-menu-item-wrapper"><div class="autocomplete-first-field"><i class="fa fa-search" aria-hidden="true"></i></div><div class="autocomplete-second-field three-dots">' +
item.label +
"</div></div>"
)
.appendTo(ul);
};
}
});
};
var changeText1 = function(e) {
if (
/[-a-z0-90áãâäàéêëèíîïìóõôöòúûüùçñ!##$%^&*()_+|~=`{}\[\]:";'<>?,.\s\/]+/gi.test(
String.fromCharCode(e.which)
)
) {
$("input").on("keypress", changeText2);
}
// DONT TOUCH THIS AREA, IT HAS NOTHING TO DO WITH THE PROBLEM
var getInputSelection = function(input) {
var start = 0,
end = 0;
input.focus();
if (
typeof input.selectionStart == "number" &&
typeof input.selectionEnd == "number"
) {
start = input.selectionStart;
end = input.selectionEnd;
} else if (document.selection && document.selection.createRange) {
var range = document.selection.createRange();
if (range) {
var inputRange = input.createTextRange();
var workingRange = inputRange.duplicate();
var bookmark = range.getBookmark();
inputRange.moveToBookmark(bookmark);
workingRange.setEndPoint("EndToEnd", inputRange);
end = workingRange.text.length;
workingRange.setEndPoint("EndToStart", inputRange);
start = workingRange.text.length;
}
}
return {
start: start,
end: end,
length: end - start
};
};
switch (e.key) {
case "Backspace":
case "Delete":
e = e || window.event;
var keyCode = e.keyCode;
var deleteKey = keyCode == 46;
var sel, deletedText, val;
val = this.value;
sel = getInputSelection(this);
if (sel.length) {
// 0 kai paprastai trini po viena o 1 ar daugiau kai select su pele trini
$("#instant-search").text(
val.substr(0, sel.start) + val.substr(sel.end)
);
} else {
$("#instant-search").text(
val.substr(0, deleteKey ? sel.start : sel.start - 1) +
val.substr(deleteKey ? sel.end + 1 : sel.end)
);
}
break;
case "Enter":
if ($("#instant-search").text()) {
console.log("Redirecting...");
}
break;
}
if (!$("#instant-search").text()) {
$("table, .ui-autocomplete").hide();
$(".wikisearch-container").css("margin-top", "");
}
if (
$(".ui-menu-item-wrapper").hasClass("ui-state-active") &&
(e.key == "ArrowRight" || e.key == "ArrowLeft")
) {
$(".ui-autocomplete").autocomplete(""); // Error metas console ir taip neturėtų būti bet nežinau kaip padaryti kad pasirinkus elementą su <-- ar --> nepadarytų tik vieno rezultato todėl paliekam laikinai ;)
}
};
$("input").on("keydown", changeText1);
$("input").on("input", function(e) {
$("#instant-search").text($("#search").val());
});
});
html,
body {
height: 100%;
width: 100%;
}
body {
margin: 0;
padding: 0;
font-family: sans-serif;
background-image: url("http://www.part.lt/img/96816a00ec1fb87adc4ca8a04365b2b5719.jpg");
background-size: cover;
background-position: 100%;
}
.v-container {
display: table;
height: 100%;
width: 100%;
}
.v-content {
display: table-cell;
vertical-align: middle;
}
.text-center {
text-align: center;
}
.input {
overflow: hidden;
white-space: nowrap;
}
.input input#search {
width: calc(100% - 30px);
height: 50px;
border: none;
font-size: 10pt;
float: left;
color: #4f5b66;
padding: 0 15px;
outline: none;
}
.ui-autocomplete {
list-style: none;
background-color: #fff;
-webkit-user-select: none;
user-select: none;
padding: 0;
margin: 0;
width: 100% !important;
top: auto !important;
display: table;
table-layout: fixed;
}
.ui-helper-hidden-accessible {
display: none;
}
.autocomplete-first-field {
width: 15%;
display: inline-block;
}
.autocomplete-second-field {
width: 85%;
display: inline-block;
text-align: left;
vertical-align: middle;
}
.three-dots {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
table {
width: 100%;
border-spacing: 0;
border-collapse: collapse;
display: none;
table-layout: fixed;
}
table tr {
background-color: #fff;
-webkit-user-select: none;
user-select: none;
}
tr:first-child {
background-color: #ffc800;
color: #fff;
}
table td,
.ui-menu-item-wrapper {
padding: 10px 0;
}
td:nth-child(2) {
width: 85%;
text-align: left;
}
.ui-menu-item,
table {
cursor: pointer;
}
:focus {
outline: 0;
}
a {
text-decoration: none;
color: #fff;
position: relative;
}
a:before {
content: "";
position: absolute;
width: 100%;
height: 0.0625rem;
bottom: 0;
left: 0;
background-color: #fff;
visibility: hidden;
-webkit-transform: scaleX(0);
transform: scaleX(0);
-webkit-transition: all 0.3s ease-in-out 0s;
transition: all 0.3s ease-in-out 0s;
}
a:hover:before {
visibility: visible;
-webkit-transform: scaleX(1);
transform: scaleX(1);
}
.search-results {
background: #fff;
margin-top: 50px;
border-left: 5px solid #0ebeff;
opacity: 0;
-webkit-transition: opacity 1s;
transition: opacity 1s;
}
.search-results h4,
.search-results p {
margin: 0;
padding: 10px;
text-align: left;
}
.search-results a {
color: #0ebeff;
display: inline-block;
margin: 1rem 0;
}
.search-results a:before {
background-color: #0ebeff;
}
.wikisearch-container {
width: 65%;
margin: 0 auto;
}
/* Loading animation */
#keyframes lds-eclipse {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
50% {
-webkit-transform: rotate(180deg);
transform: rotate(180deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
#-webkit-keyframes lds-eclipse {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
50% {
-webkit-transform: rotate(180deg);
transform: rotate(180deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
.loading {
position: relative;
top: 9.5px;
right: 15px;
pointer-events: none;
display: none;
}
.lds-eclipse {
-webkit-animation: lds-eclipse 1s linear infinite;
animation: lds-eclipse 1s linear infinite;
width: 2rem;
height: 2rem;
border-radius: 50%;
margin-left: auto;
box-shadow: 0.08rem 0 0 #0ebeff;
}
#media (max-width: 71.875em) {
.wikisearch-container {
width: 75%;
}
}
#media (max-width: 50em) {
.wikisearch-container {
width: 85%;
}
}
#media (max-width: 17.96875em) {
.wikisearch-container {
width: 100%;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<html class="no-js">
<div class="v-container">
<div class="v-content text-center">
<div class="wikisearch-container">
<div class="input">
<input type="text" id="search" placeholder="Search...">
<div class="loading">
<div class="lds-eclipse"></div>
</div>
<button class="icon"><i class="fa fa-search"></i></button>
<table>
<tr>
<td class="fa fa-search">
<td id="instant-search" class="three-dots"></td>
</tr>
</table>
</div>
<div class="search-results">
<h4></h4>
<p></p>
<a target="_blank">Click here for more</a>
</div>
</div>
</div>
</div>
EDIT 1
After some changes, the results are shown, however, before the ajax call. How can I use response() only after the ajax was successfully completed (tried using success callback, didn't work :()?
Full project code: https://codepen.io/Kestis500/pen/zRONyw?editors=0010.
Here you can see step by step how it looks like:
How it looks like when you just reloaded the page:
Let's try entering "a":
We've got some results. Ok, let's try to click on the "anarchist symbolism" element:
Results should look like "anarchist symbolism" search. However, it returns the result of the "a" search. What if we pressed "fraktur" element?
Now it shows our previous search "anarchist symbolism" results. However, it should return elements of the "fraktur" search.
EDIT 2
I've fixed many things and removed some really non sense things from my code. However, the situation with the ajax call is still the same.
https://codepen.io/Kestis500/pen/pazppP?editors=0110
Any ideas?
EDIT 3
Fixed ajax lag (now the ajax request will be sent only after the previous ajax call).
https://codepen.io/Kestis500/pen/JpPLON?editors=0110
I had to first fix a few things in your Ajax call. We then collect the results and build an array that should be returned to response(). This will populate the AutoComplete.
First we will examine the HTML. There was some closing tags missing.
HTML
<div class="v-container">
<div class="v-content text-center">
<div class="wikisearch-container">
<div class="input ui-front">
<input type="text" id="search" placeholder="Search...">
<div class="loading">
<div class="lds-eclipse"></div>
</div>
<button class="icon">
<i class="fa fa-search"></i>
</button>
<table>
<tr>
<td class="fa fa-search"></td>
<td id="instant-search" class="three-dots"></td>
</tr>
</table>
</div>
<div class="search-results">
<h4></h4>
<p></p>
<a target="_blank">Click here for more</a>
</div>
</div>
</div>
</div>
You can see the table and it's cells all have the proper closing tags now.
I didn't make any changes to your CSS or Style.
JavaScript
$(function() {
var capitalizeFirstLetter = function(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
};
$("#search").autocomplete({
source: function(request, response) {
var results = [];
$.ajax({
url: "https://en.wikipedia.org/w/api.php",
data: {
format: "json",
action: "query",
generator: "search",
gsrlimit: 6,
prop: "extracts|pageimages",
origin: "*",
pilimit: "max",
exintro: false,
explaintext: false,
exsentences: 1,
gsrsearch: request.term
},
beforeSend: function() {
$(".loading").show();
},
success: function(d) {
$(".loading").hide();
if (d.query.pages) {
$.each(d.query.pages, function(k, v) {
console.log(k, v.title, v.extract, v.pageid);
results.push({
label: v.title,
value: "https://en.wikipedia.org/?curid=" + v.pageid,
title: v.title,
extract: v.extract,
pageId: v.pageid
});
});
response(results);
}
},
datatype: "json",
cache: false
});
response(results);
},
close: function() {
if (!$(".ui-autocomplete").is(":visible")) {
$(".ui-autocomplete").show();
}
},
focus: function(e) {
e.preventDefault();
return false;
},
delay: 0,
select: function(e, ui) {
if ($(".search-results").css("opacity") != 1) {
$(".search-results h4").text(capitalizeFirstLetter(ui.item.label));
$(".search-results p").text(ui.item.extract);
$(".search-results a").prop(
"href",
ui.item.value
);
$(".search-results").css("opacity", 1);
} else if (
$(".search-results h4")
.text()
.toLowerCase() != ui.item.label
) {
$(".search-results").css("opacity", 0);
setTimeout(function() {
$(".search-results h4").text(capitalizeFirstLetter(ui.item.label));
$(".search-results p").text(ui.item.extract);
$(".search-results a").prop(
"href",
ui.item.value
);
$(".search-results").css("opacity", 1);
}, 500);
}
return false;
}
}).autocomplete("instance")._renderItem = function(ul, item) {
var $item = $("<li>");
var $wrap = $("<div>").appendTo($item);
var $field1 = $("<div>", {
class: "autocomplete-first-field"
}).appendTo($wrap);
$("<i>", {
class: "fa fa-search",
"aria-hidden": true
}).appendTo($field1);
$("<div>", {
class: "autocomplete-second-field three-dots"
}).html(item.label).appendTo($wrap);
return $item.appendTo(ul);
};
});
There was a lot of things to fix and improve.
Let's start with the Ajax. You're making a call to a MediaWiki API and expecting some results. When the call would come back, it would generate warnings about pilimit. Digging into the API docs, this is a parameter specific to the pageimages properties call. To fix this, the prop value had to be extracts|pageimages. Now I get a clean set of results.
You can see I broke out the data so that I could more easily make changes and see what parameters I was sending to the API. Nothing wrong with your method, I just find this a lot easier to work with.
This is all happening inside .autocomplete() when we are populating the source. When we use function as a source, it has to follow a few guidelines:
we pass a request and response in
results must be in an array
the array can contain objects, as long as they contain at least { label, value }
our results array must be passed to response function.
A brief example:
$(selector).autocomplete({
source: function(req, resp){
var q = req.term;
// The Request is an object that contains 1 index: term
// request.term will contain the content of our search
var results = [];
// An array to store the results
$.getJSON("myapi.php", {query: q}, function(data){
$.each(data, function(key, val){
// iterate over the result data and populate our result array
results.push({
label: data.name,
value: data.url
});
resp(results);
});
});
}
});
You can sort or filter the results all you like; as long as you pass them to response in the end.
With your focus and select callbacks, you want to return false. This is discussed more here: http://jqueryui.com/autocomplete/#custom-data
We also see a good example of rendering the menu item. I switched over to making jQuery objects versus raw HTML. You do what works best for you.
Working Example: https://jsfiddle.net/Twisty/vr6gv2aw/4/
Hope this helps.
I want to create/use an add-remove box like this in HTML using angularJS where you have a list of some objects on the left and you select the objects to put them on under the headings listed on the right. Any idea what do we call such tables/lists and can I create/find them?
I don't know what is the accurate title to call it :)
but I tried to implement a sample of 'add/remove box' to achieve the concept you want with AngularJS!
the main part consists of three sections:
left section which is going to be added
middle section which contains the buttons
right section which is going to be removed
The code below applies the above section which aforesaid:
<div class="box leftBox">
<div>
<div id="{{left.name}}" ng-click="clicked($event, 0)" data-ng-repeat="left in lefts">{{left.name}}</div>
</div>
</div>
<div class="box button_holder">
<button ng-click="add()" class="button button-blue">ADD ></button>
<button ng-click="remove()" class="button button-red">< REMOVE</button>
<button ng-click="addAll()" class="button button-blue button-dark">ADD ALL >></button>
<button ng-click="removeAll()" class="button button-red button-dark"><< REMOVE ALL</button>
<button ng-click="deleteChoice()" class="button delete">X</button>
</div>
<div class="box rightBox">
<div>
<div ng-click="clicked($event, 1)" data-ng-repeat="right in rights">{{right.name}}</div>
</div>
</div>
by using angularjs directives including data-ng-repeat you can control the procedure of adding or removing
here is the implemented overview.
here is the completed code on the "codepen".
you can also check my github.
try to run the snippet in a fullscreen window!
/**
* Created by ALIREZA on 8/30/2017.
*/
var app = angular.module('Add_Remove_Box', []);
app.controller('Ctrl', function($scope) {
var i;
var isRepeated = false;
var actionLicense = true;
var prevElement = null;
var currentElement = null;
var positionSide = null;
$scope.choices = ["left", "right"];
$scope.lefts = [{
id: 'left1'
}, {
id: 'left2'
}, {
id: 'left3'
}, {
id: 'left4'
}];
for (i = 0; i < $scope.lefts.length; i++) {
$scope.lefts[i].name = "left" + (i + 1).toString();
}
$scope.rights = [{
id: 'right1'
}, {
id: 'right2'
}];
for (i = 0; i < $scope.rights.length; i++) {
$scope.rights[i].name = "right" + (i + 1).toString();
}
$scope.insert = function($event) {
var side = $scope.selectedSide;
if (side == null || side == "left") {
var leftItemNo = $scope.lefts.length + 1;
$scope.lefts.push({
'id': 'left' + leftItemNo
});
$scope.lefts[leftItemNo - 1].name = $scope.choice.name;
} else {
var rightItemNo = $scope.rights.length + 1;
$scope.rights.push({
'id': 'right' + rightItemNo
});
$scope.rights[rightItemNo - 1].name = $scope.choice.name;
}
};
$scope.deleteChoice = function() {
if (positionSide === 0) {
var ItemNo = -1;
$scope.lefts.forEach(function(i, j) {
if (i.name === currentElement.textContent) {
ItemNo = j;
return;
}
});
$scope.lefts.splice(ItemNo, 1);
}
};
$scope.add = function() {
if (actionLicense && positionSide === 0) {
actionLicense = false;
var leftItemNo = -1;
$scope.lefts.forEach(function(i, j) {
if (i.name === currentElement.textContent) {
leftItemNo = j;
return;
}
});
$scope.lefts.splice(leftItemNo, 1);
var rightItemNo = $scope.rights.length + 1;
$scope.rights.push({
'id': 'right' + rightItemNo
});
$scope.rights[rightItemNo - 1].name = currentElement.textContent;
}
};
$scope.remove = function() {
if (actionLicense && positionSide === 1) {
actionLicense = false;
var rightItemNo = -1;
$scope.rights.forEach(function(i, j) {
if (i.name === currentElement.textContent) {
rightItemNo = j;
return;
}
});
$scope.rights.splice(rightItemNo, 1);
var leftItemNo = $scope.lefts.length + 1;
$scope.lefts.push({
'id': 'left' + leftItemNo
});
$scope.lefts[leftItemNo - 1].name = currentElement.textContent;
}
};
$scope.addAll = function() {
$scope.lefts.forEach(function(i) {
var rightItemNo = $scope.rights.length + 1;
$scope.rights.push({
'id': 'right' + rightItemNo
});
$scope.rights[rightItemNo - 1].name = i.name;
});
$scope.lefts.splice(0, $scope.lefts.length);
};
$scope.removeAll = function() {
$scope.rights.forEach(function(i) {
var leftItemNo = $scope.lefts.length + 1;
$scope.lefts.push({
'id': 'left' + leftItemNo
});
$scope.lefts[leftItemNo - 1].name = i.name;
});
$scope.rights.splice(0, $scope.rights.length);
};
$scope.clicked = function($event, pos) {
actionLicense = true;
positionSide = pos;
currentElement = $event.currentTarget;
var deleteButton = document.getElementsByClassName("delete")[0];
if (pos === 1) {
if (deleteButton.className.indexOf("button-deactive") === -1) {
deleteButton.className += " button-deactive";
}
} else {
deleteButton.className = deleteButton.className.replace(" button-deactive", "");
}
if (prevElement === null) {
prevElement = currentElement;
} else {
if (prevElement === currentElement) {
isRepeated = !isRepeated;
} else {
if (isRepeated) {
isRepeated = false;
}
}
}
if (prevElement.className.indexOf("active") !== -1) {
prevElement.className = prevElement.className.replace(" active", "");
}
if (!isRepeated && currentElement.className.indexOf("active") === -1) {
currentElement.className += " active";
}
prevElement = currentElement;
};
});
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
padding: 0;
}
.container {
display: inline-block;
width: 100%;
padding: 10px;
height: 100vh;
border: #E64A19 inset .7vh;
background: #616161;
/* fallback for old browsers */
background: -webkit-linear-gradient(to left, #9bc5c3, #616161);
/* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to left, #9bc5c3, #616161);
/* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
.addItems {
height: 10vh;
margin-bottom: 5vh;
}
.box {
float: left;
display: inline-block;
margin: 0;
padding: 10px;
height: 80vh;
border: #cfcfcf outset 2px;
color: #eee;
font-size: medium;
}
.box>div {
border: #FFF59D ridge 2px;
border-bottom: none;
border-radius: 5px;
}
.box>div>div {
padding: 7px;
border-bottom: #FFF59D ridge 2px;
}
.box>div>div:hover {
background-color: rgba(100, 150, 220, .5);
transition: background-color .4s ease;
}
.box>button {
display: inline-block;
width: 70%;
margin: 5% 15%;
}
.button {
padding: 10px 24px;
border-radius: 3px;
border: none;
box-shadow: 2px 5px 10px rgba(22, 22, 22, .1);
}
.button:hover {
transition: all 60ms ease;
opacity: .95;
box-shadow: #444 0 3px 3px 0;
}
.button-blue {
color: #FFFFFF;
background: #416dea;
}
.button-red {
color: #FFFFFF;
background: #F32C52;
}
.button-dark {
filter: brightness(85%) contrast(110%);
}
.leftBox {
width: 40%;
}
.button_holder {
width: 20%;
}
.rightBox {
width: 40%;
}
input[type="text"],
select {
padding: 5px;
}
.active {
transition: all .1s ease;
background-color: #888;
color: #000;
;
border: dotted 1px black;
box-shadow: 0 2px 2px 0 rgba(97, 97, 97, .5);
margin-bottom: 1px;
}
.button-deactive {
opacity: .5;
box-shadow: none;
}
.button-deactive:hover {
opacity: .5;
box-shadow: none;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>AddRemoveBox</title>
<link rel="stylesheet" href="index.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<!--include angularJs-->
<script src="index.js"></script>
</head>
<body>
<div class="container" ng-app="Add_Remove_Box" ng-controller="Ctrl">
<fieldset class="addItems">
<legend>Insert Items</legend>
<select ng-model="selectedSide">
<option ng-repeat="choice in choices">{{choice}}</option>
</select>
<input type="text" ng-model="choice.name" name="" placeholder="Enter The Item Name">
<button class="button insert" value="insert" ng-click="insert($event)">Insert</button>
</fieldset>
<div class="box leftBox">
<div>
<div id="{{left.name}}" ng-click="clicked($event, 0)" data-ng-repeat="left in lefts">{{left.name}}</div>
</div>
</div>
<div class="box button_holder">
<button ng-click="add()" class="button button-blue">ADD ></button>
<button ng-click="remove()" class="button button-red">< REMOVE</button>
<button ng-click="addAll()" class="button button-blue button-dark">ADD ALL >></button>
<button ng-click="removeAll()" class="button button-red button-dark"><< REMOVE ALL</button>
<button ng-click="deleteChoice()" class="button delete">X</button>
</div>
<div class="box rightBox">
<div>
<div ng-click="clicked($event, 1)" data-ng-repeat="right in rights">{{right.name}}</div>
</div>
</div>
</div>
</body>
</html>