I am using select2 in an express app to make an input box where users can select subjects from a list, and can update this list with any newly added options.
The thing I'm struggling with is that select2 runs client-side, whereas any data I use to seed my <option> tags (that I want to append new options to) is server-side.
I want users to be able to add subjects that don't exist in the original list, so that future users will be presented with newly added options (as well as the original ones)
These are the options I've considered for achieving this (in increasing desirability):
Add new <option>Subject</option> html tags for each added tag
Push new tags to an array, and seed the <option>s from this array
Seed the <option> from a json object, and update this object on tag creation
Seed the <option> from an external database (e.g. mongoose), and update this on tag creation
As far as I can see, all of these options require that my client-side code (select2-js) talks to server-side code (where my array, .json file or mongoose schema would be), and I have no idea how to go about doing this.
In my current approach I am attempting to to specify a "local" json file as my data source in my select2 call (see here). However, this doesn't seed the database with any options, so this isn't working as I expected.
I then check if each new tag exists in an array (dataBase), and add it to the database if not:
// Data to seed initial tags:
var dataBase = [
{ id: 0, text: 'Maths'},
{ id: 1, text: 'English'},
{ id: 2, text: 'Biology'},
{ id: 3, text: 'Chemistry'},
{ id: 4, text: 'Geography'}
];
$(document).ready(function() {
$('.select2-container').select2({
ajax: {
url: '../../subjects.json',
dataType: 'json',
},
width: 'style',
multiple: true,
tags: true,
createTag: function (tag) {
var isNew = false;
tag.term = tag.term.toLowerCase();
console.log(tag.term);
if(!search(tag.term, dataBase)){
if(confirm("Are you sure you want to add this tag:" + tag.term)){
dataBase.push({id:dataBase.length+1, text: tag.term});
isNew = true;
}
}
return {
id: tag.term,
text: tag.term,
isNew : isNew
};
},
tokenSeparators: [',', '.']
})
});
// Is tag in database?
function search(nameKey, myArray){
for (var i=0; i < myArray.length; i++) {
if (myArray[i].text.toLowerCase() === nameKey.toLowerCase()) {
return true
}
}
return false
};
However, this approach will add the new tags to an array that is destroyed once I refresh the page, and new tags are not stored.
How can I modify this to load server-side data (json, mongoose document or anything else that is considered a best practice), and update this data with newly added options (that pass my tests)?
On your server-side, you can have an api that maintains and returns the tag array.
If you want the array to persist even after server shutdown, you can store the tags array in a database.
Server side:
let dataBase = [
{ id: 0, text: 'Maths'},
{ id: 1, text: 'English'},
{ id: 2, text: 'Biology'},
{ id: 3, text: 'Chemistry'},
{ id: 4, text: 'Geography'}
];
//Assuming you have a nodejs-express backend
app.get('/tags', (req,res) => {
res.status(200).send({tags: dataBase});
} );
Client Side:
$(document).ready(function() {
dataBase=[];
$.get("YOUR_SERVER_ADDRESS/tags", function(data, status){
console.log("Data: " + data + "\nStatus: " + status);
dataBase = data;
});
$('.select2-container').select2({
data: dataBase,
placeholder: 'Start typing to add subjects...',
width: 'style',
multiple: true,
tags: true,
createTag: function (tag) {
var isNew = false;
tag.term = tag.term.toLowerCase();
console.log(tag.term);
if(!search(tag.term, dataBase)){
if(confirm("Are you sure you want to add this tag:" + tag.term)){
dataBase.push({id:dataBase.length+1, text: tag.term});
isNew = true;
//Update the tags array server side through a post request
}
}
return {
id: tag.term,
text: tag.term,
isNew : isNew
};
},
tokenSeparators: [',', '.']
})
});
// Is tag in database?
function search(nameKey, myArray){
for (var i=0; i < myArray.length; i++) {
if (myArray[i].text.toLowerCase() === nameKey.toLowerCase()) {
return true
}
}
return false
};
You can use select2:select and select2:unselect event for this.
var dataBase = [{
id: 0,
text: 'Maths'
},
{
id: 1,
text: 'English'
},
{
id: 2,
text: 'Biology'
},
{
id: 3,
text: 'Chemistry'
},
{
id: 4,
text: 'Geography'
}
];
$(document).ready(function() {
$('.select2-container').select2({
data: dataBase,
placeholder: 'Start typing to add subjects...',
width: 'style',
multiple: true,
tags: true,
createTag: function(tag) {
return {
id: tag.term,
text: tag.term,
isNew: true
};
},
tokenSeparators: [',', '.']
})
$(document).on("select2:select select2:unselect", '.select2-container', function(e) {
var allSelected = $('.select2-container').val();
console.log('All selected ' + allSelected);
var lastModified = e.params.data.id;
console.log('Last Modified ' + lastModified);
var dbIdArray = dataBase.map((i) => i.id.toString());
var allTagged = $('.select2-container').val().filter((i) => !(dbIdArray.indexOf(i) > -1))
console.log('All Tagged ' + allTagged);
});
});
.select2-container {
width: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/js/select2.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/css/select2.min.css" rel="stylesheet" />
<select class="select2-container"></select>
Here's what I've ended up with (thanks to both answers):
1. Set up a Mongoose DB to hold subjects:
models/subjects.js
var mongoose = require("mongoose");
var SubjectSchema = new mongoose.Schema({
subject: { type: String },
});
module.exports = mongoose.model("Subjects", SubjectSchema);
2. Set up api routes in node js express backend:
routes/api.js
var express = require("express");
var router = express.Router();
var Subjects = require("../models/subjects");
// GET route for all subjects in db
router.get("/api/subjects/all", function(req, res){
Subjects.find().lean().exec(function (err, subjects) {
return res.send(JSON.stringify(subjects));
})
});
// POST route for each added subject tag
router.post("/api/subjects/save", function(req, res){
var newSubject = {};
newSubject.subject = req.body.subject;
console.log("Updating db with:" + newSubject);
var query = {subject: req.body.subject};
var options = { upsert: true, new: true, setDefaultsOnInsert: true };
// Find the document
Subjects.findOneAndUpdate(query, options, function(error, subject) {
if (error) return;
console.log("Updated db enry: " + subject);
});
return res.send(newSubject);
});
3. Set up select2 input field:
public/js/select2.js
var dataBase=[];
$(document).ready(function() {
// Get all subjects from api (populated in step 2) and push to dataBase array
$.getJSON('/api/subjects/all')
.done(function(response) {
$.each(response, function(i, subject){
dataBase.push({id: subject._id, text: subject.subject});
})
console.log("dataBase: " + dataBase);
})
.fail(function(err){
console.log("$.getJSON('/api/subjects/all') failed")
})
// Get data from api, and on 'selecting' a subject (.on("select2:select"), check if it's in the dataBase. If it is, or the user confirms they want to add it to the database, send it to POST route, and save it to our Subjects db.
$('.select2-container')
.select2({
ajax: {
url : "/api/subjects/all",
dataType: 'json',
processResults: function (data) {
return {
results: $.map(data, function(obj) {
return { id: obj._id, text: obj.subject };
})
};
}
},
placeholder: 'Start typing to add subjects...',
width: 'style',
maximumSelectionLength: 5,
multiple: true,
createTag: function(tag) {
return {
id: tag.term,
text: tag.term.toLowerCase(),
isNew : true
};
},
tags: true,
tokenSeparators: [',', '.']
})
.on("select2:select", function(e) {
if(addSubject(dataBase, e.params.data.text)){
console.log(e.params.data.text + " has been approved for POST");
ajaxPost(e.params.data.text)
} else {
console.log(e.params.data.text + " has been rejected");
var tags = $('#selectSubject select').val();
var i = tags.indexOf(e.params.data.text);
console.log("Tags: " + tags);
if (i >= 0) {
tags.splice(i, 1);
console.log("post splice: " + tags);
$('select').val(tags).trigger('change.select2');
}
}
})
function ajaxPost(subject){
console.log("In ajaxPost");
var formData = {subject : subject}
$.ajax({
type : "POST",
contentType : "application/json",
url : "/api/subjects/save",
data : JSON.stringify(formData),
dataType : 'json'})
.done(console.log("Done posting " + JSON.stringify(formData)))
.fail(function(e) {
alert("Error!")
console.log("ERROR: ", e);
});
}
function addSubject(subjects, input) {
if (!input || input.length < 3) return false
var allSubjects = [];
$.each(subjects, function(i, subject){
if(subject.text) allSubjects.push(subject.text.toLowerCase())
});
console.log("Here is the entered subject: " + input);
if(allSubjects.includes(input)){
console.log(input + " already exists")
return true
}
if(confirm("Are you sure you want to add this new subject " + input + "?")){
console.log(input + " is going to be added to the database");
return true
}
console.log(input + " will NOT to added to the database");
return false
}
});
This works, but I would love to hear feedback on this approach!
I have posted this kind of question before but after making changes I am getting new error now so I am again posting it.I have toggle button and when i click it my current page gets freezed as i cant click on any input form or any other button and the scroll bar also disappears.
Before clicking the toggle button everything is fine:
After i click the toggle button the error appears like this and the page gets freezed(computer works fyn):
My page source code for JS and jquery is:
The html part is:
<!-- toggle switch -->
<label class="switch">
<input type="checkbox" checked="checked" value="4" />
<div class="slider"></div>
</label>
The JS part is:
$('.switch input[type="checkbox"]').on('change',function(){
var checkbox=$(this);
var checked=checkbox.prop('checked');
var dMsg=(checked)? 'You want to activate the product':
'You want to deactivate the product';
var value=checkbox.prop('value');
bootbox.confirm({
size: 'medium',
title: 'Product Activation & Deactivation',
message: dMsg,
callback: function(confirmed){
if(confirmed){
console.log(value);
bootbox.alert({
size: 'medium',
title: 'Information',
message : 'you are going to perform operation on product'+ value
});
}
else{
checkbox.prop('checked',!checked);
}
}
});
});
myapp.js
$(function(){
switch(menu){
case 'About us':
$('#about').addClass('active');
break;
case 'Contact us':
$('#contact').addClass('active');
break;
case 'All Products':
$('#listProducts').addClass('active');
break;
case 'Manage Products':
$('#manageProducts').addClass('active');
break;
default:
if(menu == "Home") break;
$('#listProducts').addClass('active');
$('#a_'+menu).addClass('active');
break;
}
//code for jquery datatable
var $table=$('#productListTable');
//execute below code only where we have this table
if($table.length){
var jsonUrl='';
if(window.categoryId== ''){
//if categoryId passed through controller is empty
jsonUrl=window.contextRoot + '/json/data/all/products';
}
else{
//if categoryId passed is there listed in listProducts.jsp
jsonUrl=window.contextRoot + '/json/data/category/'+ window.categoryId +'/products';
}
$table.DataTable({
lengthMenu:[[3,5,10,-1],['3 Records','5 Records','10 Records','ALL']],
pageLength:5,
ajax :{
url: jsonUrl,
dataSrc : ''
},
columns: [
{
data : 'code' ,
mRender: function(data,type,row){
return '<img src="'+window.contextRoot+'/resources/images/'+data+'.jpg" class="dataTableImg" />';
}
},
{
data : 'name'
},
{
data : 'brand'
},
{
data : 'unitPrice' ,
mRender:function(data,type,row){
//to make Rs. in unit price
return '₹ ' + data
}
},
{
data : 'quantity' ,
mRender:function(data,type,row){
if(data<1){
return '<span style="color:red">Out of Stock!</span>';
}
return data;
}
},{
data : 'id',
mRender: function(data,type,row){
//data means id
var str='';
str += 'View';
if(row.quantity<1){
str+='Add to Cart';
}
else{
str += 'Add to Cart';
}
return str;
}
}
]
});
}
//dismissing alert after 3sec
var $alert=$('.alert');
if($alert.length){
setTimeout(function(){
$alert.fadeOut('slow');
},3000)
}
//-------------------------------
$('.switch input[type="checkbox"]').on('change', function() {
var checkbox = $(this);
var checked = checkbox.prop('checked');
var dMsg = (checked) ? 'You want to activate the product' :
'You want to deactivate the product';
var value = checkbox.prop('value');
bootbox.confirm({
size: 'medium',
title: 'Product Activation & Deactivation',
message: dMsg,
callback: function(confirmed) {
if (confirmed) {
console.log(value);
bootbox.alert({
size: 'medium',
title: 'Information',
message: 'you are going to perform operation on product' + value
});
} else {
checkbox.prop('checked', !checked);
}
}
});
});
});
The first alert verifies whether the entered password corresponds to the user's password
If it does, then opens another alert where the user change the password if he has entered the same password in both fields
And finally, the third alert will open if it has successfully changed the password
The problem occurs if I click Cancel in the second alert or after confirming the third alert
After that, I'm not able to click on anything inside the app until I unload and restart the same application
So I guess the problem occurs because the alert is not closed properly
Here is my code:
$scope.changePass = function () {
$scope.newitem = {}
var myPopup = $ionicPopup.alert({
template: '<input type="password" placeholder="password" ng-model="newitem.password">',
title: 'Insert your password',
scope: $scope,
buttons: [
{ text: 'Cancel' },
{
text: '<b>Confirm</b>',
type: 'button-positive',
onTap: function(e) {
if (!$scope.newitem.password) {
console.log("preventing default");
e.preventDefault();
} else {
if($scope.newitem.password == $scope.user.password) {
$scope.new = {}
var newPass = $ionicPopup.alert({
template: '<input type="password" placeholder="password" ng-model="new.newpass"><br><input type="password" placeholder="Repeat password" ng-model="new.repeatpass">',
title: 'Insert your new password',
scope: $scope,
buttons: [
{ text: 'Cancel' },
{
text: '<b>Confirm</b>',
type: 'button-positive',
onTap: function(e) {
if (!$scope.new.newpass) {
console.log("preventing default");
e.preventDefault();
} else {
if (!$scope.new.repeatpass) {
$scope.new.newpass = "";
console.log("preventing default");
e.preventDefault();
} else {
if ($scope.new.newpass == $scope.new.repeatpass) {
$scope.user.password = $scope.new.newpass;
var uri = "http://someLink" + $window.localStorage.id;
$http({
method: 'PUT',
url: uri,
headers: {"Content-Type": "application/json;charset=UTF-8"},
data: $scope.user
}).success(function() {
var succesResponse = $ionicPopup.alert({
title: 'Ok',
template: "Password has changed"
});
succesResponse;
e.preventDefault();
});
}
else {
$scope.new.newpass = "";
$scope.new.repeatpass = "";
e.preventDefault();
}
}
}
}
}
]
});
}
else {
$scope.newitem.password = "";
e.preventDefault();
}
}
}
}
]
});
}
I found the answer to my question
Namely, the solution is to close the first alert before I open the second alert
But before I open second alert, it is necessary to have a timeout to close the first alert properly
myPopup.close();
$timeout(function() {
$scope.new = {}
var newPass = $ionicPopup.alert({...});
}, 500);
i want modification the base_calendar.js with new custom function like below
CalendarNotification = require('base_calendar.base_calendar');
console.log("Masuk sini bawah");
CalendarNotification.include({
'click .link2showed': function() {
console.log("ndak yo mlebu kene to");
var action = {
type: 'ir.actions.act_window',
res_model: 'crm.lead',
view_mode: 'form',
view_type: 'form',
views: [[false, 'form']],
res_id: 16644
};
this.do_action(action);
},
});
and this a base_calendar.js odoo addons
var Notification = require('web.notification').Notification;
var CalendarNotification = Notification.extend({
template: "CalendarNotification",
init: function(parent, title, text, eid) {
this._super(parent, title, text, true);
this.eid = eid;
this.events = _.extend(this.events || {}, {
'click .link2event': function() {
var self = this;
this.rpc("/web/action/load", {
action_id: "calendar.action_calendar_event_notify",
}).then(function(r) {
r.res_id = self.eid;
return self.do_action(r);
});
},
'click .link2recall': function() {
this.destroy(true);
},
'click .link2showed2': function() {
this.destroy(true);
this.rpc("/calendar/notify_ack");
},
});
},
});
How do I fix that and what causes it? I've been several times custom function JS like that and it worked well.
Thank in advance for any pointers.
I am creating an welcomescreen for my html app. and im using a welcomescreen plugin from github. you can check it here https://github.com/valnub/welcomescreen.js
now i want to show welcome screen when localstorage value is 0. and when close button of welcomescreen is clicked i am changing the localstorage value to 1. but on page refresh the localstorage value is again set to 0.
how to do that this is my js file.
/*jslint browser: true*/
/*global console, Welcomescreen, $*/
// Init method
$(document).ready(function () {
localStorage.setItem("welscreen", "0");
var welcomeTour = localStorage.getItem("welscreen");
if (welcomeTour == 0) {
$(document).ready(function () {
var options = {
'bgcolor': '#0da6ec',
'fontcolor': '#fff',
'onOpened': function () {
console.log("welcome screen opened");
console.log(welcomeTour);
},
'onClosed': function () {
localStorage.setItem("welscreen","1");
var welcomeTour = localStorage.getItem("welscreen");
console.log("welcome screen closed");
console.log(welcomeTour);
}
},
welcomescreen_slides,
welcomescreen;
welcomescreen_slides = [
{
id: 'slide0',
picture: '<div class="tutorialicon">♥</div>',
text: 'Welcome to this tutorial. In the <a class="tutorial-next-
link" href="#">next steps</a> we will guide you through a manual that will teach you how to use this app.'
},
{
id: 'slide1',
picture: '<div class="tutorialicon">✲</div>',
text: 'This is slide 2'
},
{
id: 'slide2',
picture: '<div class="tutorialicon">♫</div>',
text: 'This is slide 3'
},
{
id: 'slide3',
picture: '<div class="tutorialicon">☆</div>',
text: 'Thanks for reading! Enjoy this app or go to <a class="tutorial-previous-slide" href="#">previous slide</a>.<br><br><a class="tutorial-close-btn" href="#">End Tutorial</a>'
}
];
welcomescreen = new Welcomescreen(welcomescreen_slides, options);
$(document).on('click', '.tutorial-close-btn', function () {
welcomescreen.close();
});
$('.tutorial-open-btn').click(function () {
welcomescreen.open();
});
$(document).on('click', '.tutorial-next-link', function (e) {
welcomescreen.next();
});
$(document).on('click', '.tutorial-previous-slide', function (e) {
welcomescreen.previous();
});
});
};
});
Change this:
localStorage.setItem("welscreen", "0");
var welcomeTour = localStorage.getItem("welscreen");
to this:
var welcomeTour = localStorage.getItem("welscreen");
if(welcomeTour === undefined || welcomeTour === null) {
localStorage.setItem("welscreen", "0");
welcomeTour = "0";
}