jQuery Droppable: Only allow element to drop, if there is no other element in that dropzone - jquery-ui

I'm writing a puzzle, where you have to drag an item into the correct dropzone.
Problem: I want that you can only drag an item into a dropzone, if that dropzone does not contain any other items. How can I check, whether there are no other items in that dropzone?
Here is a gif of my current puzzle:
Here is a gif which shows the problem:
As you can see, I can drag multiple items into the same dropzone.
If a dropzone already contains an item, the user should not be able to drop another item into that dropzone. How do I achieve that?
My script so far:
$( ".draggable" ).draggable({ revert: 'invalid', snap: ".dropfield", snapTolerance: 30, snapMode: "inner"});
$( ".dropfield" ).droppable({
accept: ".dropling",
drop: function( event, ui ) {
if(some-condition){ // if correct word got dragged into the correct dropzone
var id = ui.draggable.attr('id');
$("#" + id).draggable( 'disable' );
$(this).droppable( 'disable' );
$("#" + id).css( "background-color", "#7FFF00");
}
});
Html-excerpt:
<div id="liebe" class="dropling draggable text-center">
Liebe
</div>
<span class="dropfield" value="scheitern">
</span>
PS: There are already several topics on Stack-Overflow with the same question. However, I'm not intelligent enough to apply the suggested answers to my case. Please help me.
Edit1
Here is a gif which shows my preferred behavior:
I dragged a wrong word into a dropzone. But as long that dropzone is occupied by a word, no other words should be able to be dropped into that dropzone.
My current code:
if(some-condition){ //correct word
$("#" + id).draggable( 'disable' );
$(this).droppable( 'disable' );
$("#" + id).css( "background-color", "#7FFF00");
}
} else { //wrong word
console.log("wrong word dropped");
$(this).droppable( 'disable' );
}
As soon as I drag the wrong word out of the dropzone, the dropzone should become enabled again. But how can I achieve that?

I would advise breaking this into their own functions. This way you can enable and disable drop repeatedly. Not sure what you want to trigger the item to become draggable and droppable again based on the example you have supplied. Based on what you have supplied, I can offer this the following example.
$(function() {
function enableDrop($target) {
console.log("Enabled Drop");
$target.droppable({
accept: ".dropling",
classes: {
"ui-droppable-hover": "drop-target"
},
drop: function(event, ui) {
var $that = $(this),
dragWord = ui.draggable.text().trim(),
$item = ui.draggable;
if (checkWord(dragWord)) {
console.log("Accepted: " + $item.attr("id"));
$item.
removeClass("draggable")
.draggable('disable')
.attr("style", "")
.appendTo($that);
disableDrop($that);
$that.css("background-color", "#7FFF00");
} else {
return false;
}
}
});
}
function disableDrop($target) {
console.log("Disabling Drop on " + $target.attr("class"));
$target.droppable("destroy");
}
function checkWord(w) {
var result = false;
console.log("Checked Word: " + w);
if (w == "Liebe") {
result = true;
}
return result;
}
$(".draggable").draggable({
revert: 'valid',
snap: ".dropfield",
snapTolerance: 30,
snapMode: "inner"
});
enableDrop($(".dropfield"));
});
p .dropfield {
border: 1px solid #ccc;
border-radius: 3px;
display: inline-block;
width: 4em;
height: 1.5em;
margin-bottom: -.25em
}
p .drop-target {
border: 1px dashed #ccc;
background-color: #ccc;
}
.text-center {
text-align: center;
}
.draggable {
border: 1px solid #ccc;
border-radius: 3px;
display: inline-block;
width: 4em;
height: 1em;
padding: .25em 0;
margin-bottom: -.25em
}
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<p>Diese Schlussfolgerung ist <span class="dropfield" value="scheitern"></span>: Ee kann doch nicht sein, dass es gut ist, </p>
<div id="liebe" class="dropling draggable text-center">Liebe</div>
<div id="absurd" class="dropling draggable text-center">absurd</div>

The easiest way here is probably to solve the whole thing in a more generic way. For this I would add an attribute to the respective Dom element (data-count) and then check how many characters are contained and how many are still allowed:
See /** ADDED **/ for the things i did:
$(function() {
function textWrapper(str, sp, btn) {
if (sp == undefined) {
sp = [0, 0];
}
var txt = "";
if (btn) {
txt = "<span class='w b'>" + str + "</span>";
} else {
txt = "<span class='w'>" + str + "</span>";
}
if (sp[0]) {
txt = " " + txt;
}
if (sp[1]) {
txt = txt + " ";
}
return txt;
}
function chunkWords(p) {
var words = p.split(" ");
words[0] = textWrapper(words[0], [0, 1]);
var i;
for (i = 1; i < words.length; i++) {
var re = /\[.+\]/;
if (re.test(words[i])) {
var b = makeTextBox(words[i].slice(1, -1));
words[i] = " " + b.prop("outerHTML") + " ";
} else {
if (words[0].indexOf(".")) {
words[i] = textWrapper(words[i], [1, 0]);
} else {
words[i] = textWrapper(words[i], [1, 1]);
}
}
}
return words.join("");
}
function unChunkWords(tObj) {
var words = "";
$(tObj).contents().each(function(i, el) {
if ($(el).hasClass("b")) {
words += "[" + $(el).text() + "]";
} else {
words += $(el).text();
}
});
return words.replace(/\s+/g, " ").trim();
}
function makeBtn(tObj) {
var btn = $("<span>", {
class: "ui-icon ui-icon-close"
}).appendTo(tObj);
}
function makeTextBox(txt) {
var sp = $("<span>", {
class: "w b"
}).html(txt);
makeBtn(sp);
return sp;
}
function makeDropText(obj) {
return obj.droppable({
drop: function(e, ui) {
var txt = ui.draggable.text();
var newSpan = textWrapper(txt, [1, 0], 1);
$(this).after(newSpan);
makeBtn($(this).next("span.w"));
makeDropText($(this).next("span.w"));
$("span.w.ui-state-highlight").removeClass("ui-state-highlight");
update()
},
over: function(e, ui) {
$(this).add($(this).next("span.w")).addClass("ui-state-highlight");
},
out: function() {
$(this).add($(this).next("span.w")).removeClass("ui-state-highlight");
}
});
}
$("p.given").html(chunkWords($("p.given").text()));
$("p.given").on("click", ".b > .ui-icon", function() {
$(this).parent().remove();
});
$("p.given").blur(function() {
var w = unChunkWords($(this));
$(this).html(chunkWords(w));
makeDropText($("p.given span.w"));
});
$("span.given").draggable({
helper: "clone",
revert: "invalid"
});
makeDropText($("p.given span.w"));
/** ADDED **/
// update at beginning
update();
// register update events
$("p.given").on('click keydown keyup drag drop', update);
function update(e) {
var templateText = unChunkWords($("p.given"));
var templateTextWithoutParameters = templateText.replace(/\[(.+?)\]/g, "");
var templateTextWithoutParametersLenght = templateTextWithoutParameters.length;
// calc total length
var totalLength = templateTextWithoutParametersLenght;
// since 'helper: clone' we have to ignore it!
$("[data-count]:not(.ui-draggable-dragging)").each(function(index, item) {
var count = $(item).attr("data-count")
var text = "[" + $(item).text() + "]";
var length = templateText.split(text).length - 1;
totalLength += count * length;
});
// 46,8 keycodes for delete & backspace
var maxLength = 200;
if (totalLength >= maxLength && e && e.keyCode !== 46 && e.keyCode !== 8) {
e.preventDefault();
}
// disable data counts
var remaining = maxLength - totalLength;
$("[data-count]:not(.ui-draggable-dragging)").each(function(index, item) {
var count = $(item).attr("data-count");
if (parseInt(count) > remaining) {
$(item).attr("disabled", true);
$(item).draggable().draggable('disable');
} else {
$(item).attr("disabled", false);
$(item).draggable().draggable('enable');
}
})
$(".output").text(totalLength);
}
});
p.given {
display: flex;
flex-wrap: wrap;
}
p.given span.w span.ui-icon {
cursor: pointer;
}
div.blanks {
display: inline-block;
min-width: 50px;
border-bottom: 2px solid #000000;
color: #000000;
}
div.blanks.ui-droppable-active {
min-height: 20px;
}
span.answers>b {
border-bottom: 2px solid #000000;
}
span.given {
margin: 5px;
}
/** ADDED **/
[disabled] {
color: grey
}
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<div class="row">
<p class="given" contenteditable="true">Lorem Ipsum is [Test] Ipsum has been the industry's [America] standard dummy text ever since the 1500s, </p>
</div>
<div class="divider"></div>
<div class="section">
<section>
<div class="card blue-grey ">
<div class="card-content white-text">
<div class="row">
<div class="col s12">
<span class="given btn-flat white-text red lighten-1" rel="1" data-count="50">Test</span>
<span class="given btn-flat white-text red lighten-1" rel="2" data-count="30">America</span>
<span class="given btn-flat white-text red lighten-1" rel="3" data-count="20">Qatar</span>
<span class="given btn-flat white-text red lighten-1" rel="4" data-count="10">Philippines</span>
</div>
</div>
</div>
</div>
</section>
</div>
<div class="divider"></div>
Count: <span class="output"></span>

Related

How to check if a selected string contains a substring of an highlight in epubjs

As the title above.
Assume, I have a paragraph:
It will be seen that this mere painstaking burrower and grub-worm of a poor devil of a Sub-Sub appears to have gone through the long Vaticans and street-stalls of the earth..
The bold string is a highlight. When I drag my mouse to select string
grub-worm of a poor devil of a Sub-Sub
Then I want to check if my selected text contains the highlight(or the part of the highlight) or not. How could I do that?
The code below is the example to add a highlight when I select a text.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>EPUB.js Highlights Example</title>
<script src="../dist/epub.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.min.js"></script>
<link rel="stylesheet" type="text/css" href="examples.css">
<style type="text/css">
::selection {
background: yellow;
}
#extras {
width: 600px;
margin: 40px auto;
}
#highlights {
list-style: none;
margin-left: 0;
padding: 0;
}
#highlights li {
list-style: none;
margin-bottom: 20px;
border-top: 1px solid #E2E2E2;
padding: 10px;
}
#highlights a {
display: block;
}
#viewer.spreads {
top: 0;
margin-top: 50px;
}
[ref="epubjs-mk"] {
background: url("data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPScxLjEnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZycgeG1sbnM6eGxpbms9J2h0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsnIHg9JzBweCcgeT0nMHB4JyB2aWV3Qm94PScwIDAgNzUgNzUnPjxnIGZpbGw9JyNCREJEQkQnIGlkPSdidWJibGUnPjxwYXRoIGNsYXNzPSdzdDAnIGQ9J00zNy41LDkuNEMxOS42LDkuNCw1LDIwLjUsNSwzNC4zYzAsNS45LDIuNywxMS4zLDcuMSwxNS42TDkuNiw2NS42bDE5LTcuM2MyLjgsMC42LDUuOCwwLjksOC45LDAuOSBDNTUuNSw1OS4yLDcwLDQ4LjEsNzAsMzQuM0M3MCwyMC41LDU1LjQsOS40LDM3LjUsOS40eicvPjwvZz48L3N2Zz4=") no-repeat;
width: 20px;
height: 20px;
cursor: pointer;
margin-left: 0;
}
</style>
</head>
<body>
<div id="frame">
<div id="viewer" class="spreads"></div>
<a id="prev" href="#prev" class="arrow">‹</a>
<a id="next" href="#next" class="arrow">›</a>
</div>
<div id="extras">
<ul id="highlights"></ul>
</div>
<script>
// Load the opf
var book = ePub("https://s3.amazonaws.com/moby-dick/OPS/package.opf");
var rendition = book.renderTo("viewer", {
width: "100%",
height: 600,
ignoreClass: 'annotator-hl',
manager: "continuous"
});
var displayed = rendition.display(6);
// Navigation loaded
book.loaded.navigation.then(function(toc){
// console.log(toc);
});
var next = document.getElementById("next");
next.addEventListener("click", function(){
rendition.next();
}, false);
var prev = document.getElementById("prev");
prev.addEventListener("click", function(){
rendition.prev();
}, false);
var keyListener = function(e){
// Left Key
if ((e.keyCode || e.which) == 37) {
rendition.prev();
}
// Right Key
if ((e.keyCode || e.which) == 39) {
rendition.next();
}
};
rendition.on("keyup", keyListener);
document.addEventListener("keyup", keyListener, false);
rendition.on("relocated", function(location){
// console.log(location);
});
// Apply a class to selected text
rendition.on("selected", function(cfiRange, contents) {
rendition.annotations.highlight(cfiRange, {}, (e) => {
console.log("highlight clicked", e.target);
});
contents.window.getSelection().removeAllRanges();
});
this.rendition.themes.default({
'::selection': {
'background': 'rgba(255,255,0, 0.3)'
},
'.epubjs-hl' : {
'fill': 'yellow', 'fill-opacity': '0.3', 'mix-blend-mode': 'multiply'
}
});
// Illustration of how to get text from a saved cfiRange
var highlights = document.getElementById('highlights');
rendition.on("selected", function(cfiRange) {
book.getRange(cfiRange).then(function (range) {
var text;
var li = document.createElement('li');
var a = document.createElement('a');
var remove = document.createElement('a');
var textNode;
if (range) {
text = range.toString();
textNode = document.createTextNode(text);
a.textContent = cfiRange;
a.href = "#" + cfiRange;
a.onclick = function () {
rendition.display(cfiRange);
};
remove.textContent = "remove";
remove.href = "#" + cfiRange;
remove.onclick = function () {
rendition.annotations.remove(cfiRange);
return false;
};
li.appendChild(a);
li.appendChild(textNode);
li.appendChild(remove);
highlights.appendChild(li);
}
})
});
</script>
</body>
</html>
I assume you only know the functionality of epubjs you listed above. From rendition.on(selected,...), we can get output: cfiRange. From book.getRange(cfiRange).then(function (range)..., we can get output: range.
That means whenever we select a word or sentence, we get cfiRange and range.
cfiRange is epubcfi(/6/10[id139]!/4/2[filepos12266]/6,/3:1,/3:4, which based on position of the selected word. I don't know how it calculates/works but if you do then you can check if the cfiRange contains a existing highlight word's cfiRange.
range.toString() can give you the text. if your application is only storing a word. then you can check if the new selected word == or contain your existing highlight word.

Revert draggable after a different draggable is dropped

I have a 2x2 grid of droppable areas [[A,B][C,D]] and under the grid is a 1x4 grid of draggables. I only want certain draggables next to each other. So for example, if there is a draggable in B, and I drag a different draggable into A, is there a way to make the draggable in B revert? The draggables have data-row and data-col so that I can grab the draggable in the prev/next column if I need to.
$(".draggable").draggable({
scroll: false,
snap: ".snaptarget",
snapMode: "inner",
stack: ".draggable",
revert: function (event, ui) {
var $draggable = $(this);
$draggable.data("uiDraggable").originalPosition = {
top: 0,
left: 0
};
return !event;
}
});
$(".snaptarget").droppable({
accept: ".draggable",
drop: function (event, ui) {
var $draggable = $(ui.draggable);
var $droppable = $(this);
// This droppable is taken, so don't allow other draggables
$droppable.droppable('option', 'accept', ui.draggable);
ui.draggable.position({
my: "center",
at: "center",
of: $droppable,
using: function (pos) {
$draggable.animate(pos, "fast", "linear");
}
});
// Disable prev or next droppable if the pagewidth == 1
if ($droppable.data("col") == 1) {
$droppable.next().droppable("option", "disabled", true);
var nextDrag = $(".draggable[data-row='" + $droppable.data("row") + "'][data-col='2']");
if (nextDrag.length) {
// I need to revert nextDrag if there is one.
// I've tried this but it doesn't seem to work
nextDrag.data("uiDraggable").originalPosition = {
top: 0,
left: 0
}
}
}
},
tolerance: "pointer"
});
Took a little bit of work, I am never good with offsets and positioning. Here's the key:
function returnItem(item, target) {
// Get Origin
var oPos = item.data("uiDraggable").originalPosition;
// Adjust Postion using animation
item.position({
my: "top left",
at: "top left+" + oPos.left,
of: target,
using: function(pos) {
item.animate(pos, "fast", "linear");
}
});
}
Here is a working example based on the Draggable Snap to element grid example:
https://jsfiddle.net/Twisty/a4ucb6y3/6/
HTML
<div id="target">
<div class="snaptarget ui-widget-header" data-col="1" data-row="1" style="top: 0; left: 0;">
</div>
<div class="snaptarget ui-widget-header" data-col="2" data-row="1" style="top: 0; left: 80px;">
</div>
<div class="snaptarget ui-widget-header" data-col="1" data-row="2" style="top: 80px; left: 0;">
</div>
<div class="snaptarget ui-widget-header" data-col="2" data-row="2" style="top: 80px; left: 80px;">
</div>
</div>
<br style="clear:both">
<div id="source">
<div id="drag-A" class="draggable ui-widget-content" style="left: 0;">
<p>Drag A</p>
</div>
<div id="draggable2" class="draggable ui-widget-content" style="left: 80px;">
<p>Drag B</p>
</div>
<div id="draggable3" class="draggable ui-widget-content" style="left: 160px;">
<p>Drag C</p>
</div>
<div id="draggable4" class="draggable ui-widget-content" style="left: 240px;">
<p>Drag D</p>
</div>
</div>
CSS
.draggable {
width: 80px;
height: 80px;
font-size: .9em;
position: absolute;
top: 0;
}
.draggable p {
text-align: center;
height: 1em;
margin-top: 30px;
}
#source {
width: 320px;
height: 80px;
position: relative;
}
#target {
width: 160px;
height: 160px;
position: relative
}
.snaptarget {
width: 80px;
height: 80px;
position: absolute;
}
jQuery
$(function() {
function returnItem(item, target) {
// Get Origin
var oPos = item.data("uiDraggable").originalPosition;
// Adjust Postion using animation
di.position({
my: "top left",
at: "top left+" + oPos.left,
of: target,
using: function(pos) {
item.animate(pos, "fast", "linear");
}
});
}
$(".draggable").draggable({
scroll: false,
snap: ".snaptarget",
snapMode: "inner",
stack: ".draggable",
revert: "invalid",
start: function(e, ui) {
var off = $("#source").position();
ui.helper.data("uiDraggable").originalPosition = {
top: ui.position.top,
left: ui.position.left
};
}
});
$(".snaptarget").droppable({
accept: ".draggable",
drop: function(event, ui) {
var $draggable = $(ui.draggable);
var $droppable = $(this);
// This droppable is taken, so don't allow other draggables
$droppable.droppable('option', 'accept', ui.draggable);
// Disable prev or next droppable if the pagewidth == 1
if ($droppable.data("col") == 1) {
$droppable.next().droppable("option", "disabled", true);
var nextDrag = $(".draggable[data-row='" + $droppable.data("row") + "'][data-col='2']");
if (nextDrag.length) {
// I need to revert nextDrag if there is one.
returnItem(nextDrag, $("#source"));
}
}
},
tolerance: "pointer"
});
});
In draggable, when we start to drag, we want to record the original position (in case we need to later revert). The revert option is set to invalid in case the user drags it off some other place weird.
We add the position to data of the dragged item so that it can be read later.
When that item is dropped is when the magic happens. You had done all the checking, just needed to return the item if it didn't fit. if nextDrag exists, we return it to it's source.
Going forward, you may want to consider appending, cloning, and removing the elements in the start/stop events. As it is now, we're really only adjust the positioning of the elements, not their hierarchy in the DOM. Depending on what your needs are, this may not matter.

Building a chat app that uses a node.js server in IOS [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I am trying to build an iPhone(native) chat app that uses node.js on socket.io.
What is the best way to create chat application on IOS
Is there is any way to create chat application with the node.js server in IOS
Could anyone give me suggestion?
Thanks for you suggestion
Of cuz you could create chat application using Socket.io with iOS/Android and HTML!
There are 2 ways for you to do it!
i) Implement your own socket communication with Socket.io, (this is diffucult, because you need to write most of the network implementation by yourself!)
Socket.io will interface as stream, that you need to connect from your iOS!
You could refer the iOS dev guide on how to implement the stream!
https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/UsingSocketsandSocketStreams.html
There is an active thread that discuss about this too!
iPhone Objective-C socket communication with Socket.IO
ii) Use existing library or wrapper that people make for iOS, you could find the link below!
This library mainly done the networking part, and you just need to implement your app logics!
iOS
https://github.com/pkyeck/socket.IO-objc
Android
https://github.com/nkzawa/socket.io-client.java
It would be good for you to start with the library first and then try to make your own implementation! :))
I suggest following:
Develop HTML5 application with node.js and NOSQL database (CouchDb in my example) on the server
Do not use socket.io for your first playground, because its complex. You must first understand the websockets very well.
Do not burden yourself with pre-prepared frameworks and tons of code. You need a clear, simple code in order to make modifications you need later. You need full control and understanding of the code you have.
I attached working sample (thanks to Ales Smodis). For the sample bellow to work, you need to install 2 node.js packets:
npm install websocket
npm install nano
And you need to create databse and insert at least 1 user into CouchDb database:
curl -X PUT http://localhost:5984/chatroom
curl -X PUT -H 'Content-Type: application/json' --data '{"username":"john","password":"john"}' http://localhost:5984/chatroom/john
$(document).ready(function () {
var connection,
username = 'Tester',
password = 'Tester',
historyCounter = 0,
members = {},
// displays
jqLogin = $('#login'),
jqChatroom = $('#chatroom'),
// login display components
jqWaiting = $('#waiting'),
jqNickname = $('#nickname'),
jqPassword = $('#password'),
// chat display components
jqHistory = $('#history'),
jqMembers = $('#members'),
jqLine = $('#line');
function addLine(nick, line) {
var jq = $('<p><span class="nick"></span><span class="line"></span></p>'),
jqNick = jq.find('.nick'),
jqLine = jq.find('.line'),
i, lines;
jqNick.text(nick ? nick + ': ' : '*** ');
jqLine.text(line);
jqHistory.append(jq);
historyCounter++;
for (lines = jqHistory.children(), i = 0; historyCounter > 100; i++, historyCounter--) {
$(lines[i]).remove();
}
}
function addMsg(msgObj) {
var msgHandler = states[activeState].messageHandlers[msgObj.type] || function () { addLine(null, 'Neveljaven paket tipa ' + msgObj.type); };
msgHandler(msgObj);
}
function clearMembers() {
var nickname;
for (nickname in members) {
members[nickname].remove();
delete members[nickname];
}
jqMembers.empty(); // just in case
}
function addMember(nickname) {
var jq = $('<li></li>');
jq.text(nickname);
jqMembers.append(jq);
members[nickname] = jq;
}
function removeMember(nickname) {
if (nickname in members) {
members[nickname].remove();
delete members[nickname];
}
}
function connect () {
connection = new WebSocket('ws://127.0.0.1:8080');
connection.onopen = function () {
states[activeState].onopen();
};
connection.onmessage = function (message) {
try {
addMsg(JSON.parse(message.data));
}
catch (e) {
addLine(null, 'Exception while handling a server message: ' + e.toString());
}
};
connection.onclose = function () {
states[activeState].onclose();
};
}
function loginKeypress(event) {
if (13 !== event.keyCode) return;
username = jqNickname.val();
password = jqPassword.val();
if (!username) jqNickname[0].focus();
else if (!password) jqPassword[0].focus();
else {
jqWaiting.css('display', '');
jqNickname.unbind('keydown', loginKeypress);
jqPassword.unbind('keydown', loginKeypress);
connect();
}
}
function inputKeypress(event) {
var line;
if (13 === event.keyCode) {
line = jqLine.val();
if (line.length === 0) return;
jqLine.val('');
connection.send(JSON.stringify({ 'type': 'line', 'line': line }));
}
}
var states = {
'login': {
'start': function () {
jqChatroom.css('display', 'none');
jqWaiting.css('display', 'none');
jqLogin.css('display', '');
jqNickname.val('');
jqPassword.val('');
jqNickname[0].focus();
activeState = 'login';
jqNickname.bind('keydown', loginKeypress);
jqPassword.bind('keydown', loginKeypress);
},
'onopen': function () {
connection.send(JSON.stringify({ 'type': 'login', 'username': username, 'password': password }));
},
'messageHandlers': {
'state': function (msgObj) {
var i, history, users;
states.chat.start();
history = msgObj.history;
jqHistory.empty();
historyCounter = 0;
for (i = 0; i < history.length; i++) addMsg(history[i]);
users = msgObj.users;
clearMembers();
for (i = 0; i < users.length; i++) addMember(users[i]);
}
},
'unhandledMessage': function (msgObj) {
connection.close(4000, 'Unhandled message type');
},
'onclose': function () {
states.login.start();
}
},
'chat': {
'start': function () {
jqLogin.css('display', 'none');
jqWaiting.css('display', 'none');
jqChatroom.css('display', '');
jqHistory.empty();
historyCounter = 0;
activeState = 'chat';
jqLine.bind('keydown', inputKeypress);
},
'onopen': function () {
connection.close(4001, 'Connection opened while chatting');
},
'messageHandlers': {
'line': function (msgObj) {
addLine(msgObj.nick, msgObj.line);
},
'join': function (msgObj) {
addLine(null, 'Priklopil: ' + msgObj.nick);
addMember(msgObj.nick);
},
'leave': function (msgObj) {
addLine(null, 'Odklopil: ' + msgObj.nick);
removeMember(msgObj.nick);
}
},
'unhandledMessage': function (msgObj) {
connection.close(4000, 'Unhandled message type');
},
'onclose': function () {
addLine(null, 'Connection closed');
jqLine.unbind('keydown', inputKeypress);
}
}
},
activeState = 'login';
states.login.start();
});
// node.js code
var http = require('http'),
url = require('url'),
path = require('path'),
fs = require('fs'),
nano = require('nano')('http://localhost:5984'),
chatroomDb = nano.use('chatroom'),
websocket = require('websocket'),
chatHistory = [],
activeUsers = {};
var filesDir = path.join(process.cwd(), 'web');
var mimeTypes = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'text/javascript'
};
var getContentType = function (extension) {
var mimeType = mimeTypes[extension];
return mimeType ? mimeType : 'application/octet-stream';
};
var server = http.createServer(function (request, response) {
var relativePath = url.parse(request.url).pathname,
absolutePath = path.join(filesDir, relativePath);
var handler = function (err, stats) {
if (stats) {
if (stats.isDirectory()) {
absolutePath = path.join(absolutePath, 'index.html');
fs.stat(absolutePath, handler);
return;
}
if (stats.isFile()) {
response.writeHead(200, getContentType(path.extname(absolutePath)));
var stream = fs.createReadStream(absolutePath);
stream.pipe(response);
return;
}
}
response.writeHead(404, {'Content-Type': 'text/plain'});
response.write('Not found\r\n');
response.end();
};
console.log('HTTP request for ' + relativePath);
fs.stat(absolutePath, handler);
});
server.listen(8080, function () {});
wsServer = new websocket.server({
'httpServer': server
});
function addLine (type, nick, line) {
var msg = { 'type': type, 'nick': nick, 'line': line },
jsonMsg = JSON.stringify(msg),
username;
chatHistory.push(msg);
while (chatHistory.length > 100) chatHistory.shift();
for (username in activeUsers) {
activeUsers[username].sendMessage(jsonMsg);
}
}
wsServer.on('request', function (request) {
console.log('New websocket connection from ' + request.origin);
// TODO: verify that request.origin is our web site
var connection = request.accept(null, request.origin);
var username = null;
connection.on('message', function (message) {
if (message.type !== 'utf8') {
console.log('Refusing a non-utf8 message');
return;
}
console.log('Processing message: ' + message.utf8Data);
try {
var m = JSON.parse(message.utf8Data);
switch (m.type) {
case 'login':
chatroomDb.get(m.username, function (err, body) {
if (err || (body.password !== m.password)) {
connection.close();
return;
}
username = m.username;
addLine('join', username, null);
activeUsers[username] = {
'sendMessage': function (jsonMsg) {
connection.sendUTF(jsonMsg);
}
};
var users = [], u;
for (u in activeUsers) users.push(u);
connection.sendUTF(JSON.stringify({ 'type': 'state', 'history': chatHistory, 'users': users }));
});
break;
case 'line':
if (!username) {
connection.close();
break;
}
addLine('line', username, m.line);
break;
}
}
catch (e) {
console.log(e);
}
});
connection.on('close', function (connection) {
console.log('Connection closed');
if (username) {
delete activeUsers[username];
addLine('leave', username, null);
}
});
});
console.log('Server running');
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Chatroom</title>
<style>
html, body {
width: 100%;
height: 100%;
padding: 0;
border: none;
margin: 0;
}
#heading {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 30px;
margin: 0;
padding: 0;
line-height: 30px;
text-align: center;
font-size: 20px;
background-color: green;
}
#outer {
position: absolute;
top: 30px;
bottom: 0;
left: 0;
right: 0;
margin: 20px;
min-height: 400px;
min-width: 400px;
background-color: lime;
}
#inner {
height: 100%;
background-color: #ffc0cb;
}
#chat {
position: absolute;
top: 0;
left: 0;
right: 200px;
bottom: 0;
background-color: #ffd700;
}
#members {
position: absolute;
top: 0;
right: 10px;
width: 180px;
bottom: 0;
background-color: #ff00ff;
list-style-type: none;
padding: 10px;
padding: 0;
border: none;
}
#history {
position: absolute;
top: 0;
left: 0;
bottom: 2em;
right: 0;
background-color: #00ffff;
padding: 10px;
}
#input {
position: absolute;
height: 2em;
left: 0;
right: 0;
bottom: 0;
background-color: #90ee90;
line-height: 2em;
}
#line {
width: 100%;
margin: 0;
border: none;
padding: 0;
}
#history > p {
margin: 2px;
}
.nick {
white-space: pre;
display: table-cell;
}
.line {
display: table-cell;
}
#login {
height: 100%;
display: table;
margin: 0 auto;
}
#login > .svg {
vertical-align: middle;
display: table-cell;
}
#login-wrapper1 {
display: table;
height: 100%;
width: 100%;
}
#login-wrapper2 {
display: table-cell;
vertical-align: middle;
}
#login-table {
margin: 0 auto;
}
#login-table .label {
text-align: right;
}
#waiting {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
opacity: 0.3;
}
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="client.js"></script>
</head>
<body>
<div id="login">
<div class="svg">
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 400 400"
width="400"
height="400">
<defs>
<path id="curved-text" d="M0,0 c 50 -50 150 -50 200 0" />
</defs>
<g>
<text
transform="translate(-100,40)"
font-weight="bold"
font-variant="small-caps"
font-family="Arial sans-serif"
font-size="30"
fill="none"
stroke="orange"
text-anchor="middle">
<textPath xlink:href="#curved-text" startOffset="50%">Chatroom</textPath>
</text>
<animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0" to="360" dur="5s" fill="remove" additive="sum" repeatCount="indefinite" />
<animateMotion dur="10s" repeatCount="indefinite" path="M100,200 a100,100 0 1 1 200,0 a100,100 0 1 1 -200,0" />
</g>
<foreignObject
x="0"
y="0"
width="400"
height="400"
style="height:400px;">
<div xmlns="http://www.w3.org/1999/xhtml" id="login-wrapper1">
<div id="login-wrapper2">
<table id="login-table">
<tbody>
<tr>
<td class="label">Vzdevek:</td><td><input type="text" id="nickname" /></td>
</tr>
<tr>
<td class="label">Geslo:</td><td><input type="password" id="password" /></td>
</tr>
</tbody>
</table>
</div>
</div>
</foreignObject>
</svg>
</div>
</div>
<div id="chatroom" style="display:none;">
<h1 id="heading">Chatroom</h1>
<div id="outer">
<div id="inner">
<div id="chat">
<div id="history">
<p><span class="nick">Matej: </span><span class="line">Hi</span></p>
<p><span class="nick">Borut: </span><span class="line">How are you?</span></p>
<p><span class="nick">Martina: </span><span class="line">Ok, thanks!</span></p>
</div>
<div id="input"><input id="line" type="text"></div>
</div>
<ul id="members">
<li>Matej</li>
<li>Borut</li>
<li>Martina</li>
</ul>
</div>
</div>
</div>
<div id="waiting" style="display:none;"></div>
</body>
</html>
If you are evaluating options, check out IP Messaging from Twilio:
https://www.twilio.com/docs/tutorials/walkthrough/ip-chat/ios/swift#0
The tutorial above in Swift for iOS (also available for Objective-C) allows you to work with a native SDK while the server side app (yours in Node.js) generates user access tokens to connect to the API.
The code walks you through joining a channel, creating a channel and sending messages.
Note: I do work for Twilio. This quick sample app should at least help you determine what functionality will work best for you.

jqGrid:Font Awesome Icons

I am trying to use Font Awesome icons in place of the jqueryUI icons for the toolbar in my jqGrid (add,edit,delete,view icons).
This demo is exactly what I would like to accomplish. I've read Oleg's answer that demonstrates removing the icon class and adding the Font Awesome icons in its place. But when I try to do that nothing changes. I believe I'm possibly referencing the icons wrong.
I downloaded Font Awesome 4.0.3 and I have jqGrid 4.5.4--In the _icons.scss file of the FA file tree the icons are referenced like this:
.#{$fa-css-prefix}-pencil:before { content: $fa-var-pencil; }
But in Oleg's suggested code the new icons are labeled "icon-pencil":
$grid.jqGrid("navGrid", "#pager", {editicon: "icon-pencil",
addicon: "icon-plus", delicon: "icon-trash", searchicon: "icon-search",
refreshicon: "icon-refresh", viewicon: "icon-file",view: true});
$("#pager .navtable .ui-pg-div>span").removeClass("ui-icon");
This is my code: I only did the edit icon for this example. I also used the new label for the icons, "fa-pencil".
jQuery("#grid").jqGrid('navGrid','#grid_toppager"', {editicon: "fa-pencil", edit:true});
$('#grid_toppager .navtable .ui-pg-div>span').removeClass('ui-icon');
What combination of code do I need in order to replace the ui-icons with the Font Awesome icons?
Any helpful tips would be appreciated, thanks
I agree that my old answer can't be used with Font Awesome 4 because the names of the classes are changed in version 4. I use Font Awesome 4 myself in solutions which I develop for my customers and I decide to share it with other.
The files jQuery.jqGrid.fontAwesome4.css, jQuery.jqGrid.fontAwesome4.js and jQuery.jqGrid.checkboxFontAwesome4.js contains new jqGrid method initFontAwesome and formatter: "checkboxFontAwesome4". The demo demonstrates the usage of the files:
The usage of suggested method initFontAwesome is very simple. First of all one need to include additional CSS and JavaScript files:
<link rel="stylesheet" type="text/css"
href="http://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css">
...
<link rel="stylesheet" type="text/css" href=".../ui.jqgrid.css" />
<link rel="stylesheet" type="text/css" href=".../jQuery.jqGrid.fontAwesome4.css" />
...
<script type="text/javascript" src=".../i18n/grid.locale-en.js"></script>
<script type="text/javascript" src=".../jquery.jqGrid.min.js"></script>
<script type="text/javascript" src=".../jQuery.jqGrid.fontAwesome4.js"></script>
Then one modify well known line
$("#grid").jqGrid({
... // jqGrid options and callbacks
});
to
$("#grid").jqGrid("initFontAwesome").jqGrid({
... // jqGrid options and callbacks
});
To use formatter: "checkboxFontAwesome4" instead of predefined formatter formatter: "checkbox" one need just includes jQuery.jqGrid.checkboxFontAwesome4.js after jquery.jqGrid.min.js (or jquery.jqGrid.src.js):
<script type="text/javascript"
src=".../jQuery.jqGrid.checkboxFontAwesome4.js"></script>
The formatter "checkboxFontAwesome4" have some advantage to formatter: "checkbox":
one can select the row by clicking on the icons. The standard formatter: "checkbox" uses disabled <input type="checkbox">. Clicking on disabled control will be blocked on the most web browsers. I posted before "clickableCheckbox" (see here and here).
The tests which I made with grids having many rows and columns using the tree checkbox formatters shows that formatter "checkboxFontAwesome4" is the most quick in rendering (in all web browsers where I it tested), formatter: "checkbox" is lower and "clickableCheckbox" is the mostly slow. So formatter "checkboxFontAwesome4" is not only cool, but it's really quick in rendering.
At the end I includes the current state of jQuery.jqGrid.fontAwesome4.css, jQuery.jqGrid.fontAwesome4.js and jQuery.jqGrid.checkboxFontAwesome4.js:
jQuery.jqGrid.fontAwesome4.css:
.ui-jqgrid .ui-pg-table .ui-pg-div>span.fa, #jqContextMenu .ui-menu-item>a>span.fa {
text-indent:0;
height: auto;
width: auto;
background-image: none;
overflow: visible;
padding-top: 1px;
}
.ui-jqgrid .ui-pg-table .ui-pg-div {
text-indent:0;
height: auto;
width: auto;
background-image: none;
overflow: visible;
padding-top: 1px;
}
.ui-jqgrid .ui-jqgrid-titlebar-close.fa-title span { font-size: 18px; display: inline-block; }
.ui-jqgrid .ui-jqgrid-titlebar-close.fa-title { margin-top: 0; top: 0; padding-left: 2px; padding-bottom: 2px;}
.ui-jqgrid .ui-icon-asc.fa { height: auto; margin-top: 0; }
.ui-jqgrid .ui-icon-asc.fa, .ui-jqgrid .ui-icon-desc.fa {
height: auto; margin-top: 2px; margin-left: 2px;
}
.ui-jqgrid .s-ico>.ui-state-disabled.fa, .s-ico>.ui-state-disabled.fa { padding: 0; }
.ui-jqdialog .ui-jqdialog-titlebar-close { text-decoration: none; right: 0.2em !important}
.ui-jqdialog .ui-jqdialog-titlebar-close>span { margin-top: 3px; margin-left: 5px;}
.ui-jqdialog .EditTable .fm-button-icon-right { padding-left: 0; padding-right: 0.5em; float:right;}
.ui-jqdialog .EditTable .fm-button-icon-left { padding-left: 0; float:left; }
.ui-jqdialog .EditButton>.fm-button { display: block; width: auto; }
.ui-jqdialog .EditButton>.fm-button>span { float: left; margin-left: 0.5em; margin-right: 0;}
.ui-jqgrid .ui-jqdialog .fm-button>span { margin-left: 0.5em; margin-right: 0; }
.ui-jqdialog>.ui-resizable-se { bottom: -3px; right: -3px}
jQuery.jqGrid.fontAwesome4.js:
/*global $ */
(function ($) {
"use strict";
/*jslint unparam: true */
$.extend($.jgrid, {
icons: {
common: "fa", // will be implemented later
scale: "", // will be implemented later. For example as "fa-lg"
titleVisibleGrid: "fa fa-arrow-circle-up",
titleHiddenGrid: "fa fa-arrow-circle-down",
titleIcon: "ui-corner-all fa-title",
close: "fa fa-times",
edit: "fa fa-pencil fa-fw",
add: "fa fa-plus fa-fw",
del: "fa fa-trash-o fa-fw",
search: "fa fa-search fa-fw",
refresh: "fa fa-refresh fa-fw",
view: "fa fa-file-o fa-fw",
pager: {
first: "fa fa-step-backward fa-fw",
prev: "fa fa-backward fa-fw",
next: "fa fa-forward fa-fw",
last: "fa fa-step-forward fa-fw"
},
form: {
prev: "fa fa-caret-left",
next: "fa fa-caret-right",
save: "fa fa-floppy-o",
undo: "fa fa-undo",
close: "fa fa-times",
delete: "fa fa-trash-o"
},
searchForm: {
reset: "fa fa-undo",
query: "fa fa-comments-o",
search: "fa fa-search"
}
}
});
$.extend($.jgrid.nav, {
editicon: $.jgrid.icons.edit,
addicon: $.jgrid.icons.add,
delicon: $.jgrid.icons.del,
searchicon: $.jgrid.icons.search,
refreshicon: $.jgrid.icons.refresh,
viewicon: $.jgrid.icons.view
});
$.extend($.jgrid.defaults, {
fontAwesomeIcons: true // the new option will be used in callbacks
});
$.extend($.jgrid, {
originalCreateModal: $.jgrid.originalCreateModal || $.jgrid.createModal,
createModal: function (aIDs, content, p, insertSelector, posSelector, appendsel, css) {
$.jgrid.originalCreateModal.call(this, aIDs, content, p, insertSelector, posSelector, appendsel, css);
if ($(insertSelector).find(">.ui-jqgrid-bdiv>div>.ui-jqgrid-btable").jqGrid("getGridParam", "fontAwesomeIcons")) {
$("#" + $.jgrid.jqID(aIDs.modalhead) + ">a.ui-jqdialog-titlebar-close>span.ui-icon")
.removeClass("ui-icon ui-icon-closethick")
.addClass($.jgrid.icons.close);
$("#" + $.jgrid.jqID(aIDs.themodal) + ">div.jqResize").removeClass("ui-icon-grip-diagonal-se");
}
}
});
$.extend($.jgrid.view, {
beforeShowForm: function ($form) {
var $dialog = $form.closest(".ui-jqdialog"),
$iconSpans = $dialog.find("a.fm-button>span.ui-icon");
$iconSpans.each(function () {
var $this = $(this), $fmButton = $this.parent();
if ($this.hasClass("ui-icon-triangle-1-w")) {
$this.removeClass("ui-icon ui-icon-triangle-1-w")
.addClass($.jgrid.icons.form.prev);
} else if ($this.hasClass("ui-icon-triangle-1-e")) {
$this.removeClass("ui-icon ui-icon-triangle-1-e")
.addClass($.jgrid.icons.form.next);
} else if ($this.hasClass("ui-icon-close")) {
$fmButton.removeClass("fm-button-icon-left")
.addClass("fm-button-icon-right")
.html("<span class=\"" + $.jgrid.icons.form.close + "\"></span><span>" + $fmButton.text() + "</span>");
}
});
}
});
$.extend($.jgrid.del, {
afterShowForm: function ($form) {
var $dialog = $form.closest(".ui-jqdialog"),
$tdButtons = $dialog.find(".EditTable .DelButton"),
$fmButtonNew = $("<td class=\"DelButton EditButton\" style=\"float: right;\">"),
$iconSpans = $tdButtons.find(">a.fm-button>span.ui-icon");
$tdButtons.css("float", "right");
$iconSpans.each(function () {
var $this = $(this), $fmButton = $this.parent();
if ($this.hasClass("ui-icon-scissors")) {
$fmButton.html("<span class=\"" + $.jgrid.icons.form.delete + "\"></span><span>" + $fmButton.text() + "</span>");
$fmButtonNew.append($fmButton);
} else if ($this.hasClass("ui-icon-cancel")) {
$fmButton.html("<span class=\"" + $.jgrid.icons.form.undo + "\"></span><span>" + $fmButton.text() + "</span>");
$fmButtonNew.append($fmButton);
}
});
if ($fmButtonNew.children().length > 0) {
// remove between buttons
$tdButtons.replaceWith($fmButtonNew);
}
}
});
$.jgrid.extend({
initFontAwesome: function () {
return this.each(function () {
var $grid = $(this);
$grid.bind("jqGridFilterAfterShow", function (e, $form) {
// an alternative to afterShowSearch
var $dialog = $form.closest(".ui-jqdialog"),
$iconSpans = $dialog.find("a.fm-button>span.ui-icon");
$iconSpans.each(function () {
var $this = $(this), $fmButton = $this.parent();
$this.removeClass("ui-icon");
if ($this.hasClass("ui-icon-search")) {
$this.closest(".EditButton").css("float", "right");
$fmButton.addClass("fm-button-icon-right")
.html("<span class=\"" + $.jgrid.icons.searchForm.search + "\"></span><span>" + $fmButton.text() + "</span>");
} else if ($this.hasClass("ui-icon-arrowreturnthick-1-w")) {
$this.closest(".EditButton").css("float", "left");
$fmButton.addClass("fm-button-icon-left")
.html("<span class=\"" + $.jgrid.icons.searchForm.reset + "\"></span><span>" + $fmButton.text() + "</span>");
} else if ($this.hasClass("ui-icon-comment")) {
$this.closest(".EditButton").css("float", "right");
$fmButton.addClass("fm-button-icon-right")
.html("<span class=\"" + $.jgrid.icons.searchForm.query + "\"></span><span>" + $fmButton.text() + "</span>");
}
});
}).bind("jqGridAddEditBeforeShowForm", function (e, $form) {
// alternative to beforeShowForm callback
var $dialog = $form.closest(".ui-jqdialog"),
$iconSpans = $dialog.find("a.fm-button>span.ui-icon");
$iconSpans.each(function () {
var $this = $(this), $fmButton = $this.parent();
if ($this.hasClass("ui-icon-triangle-1-w")) {
$this.removeClass("ui-icon ui-icon-triangle-1-w")
.addClass($.jgrid.icons.form.prev);
} else if ($this.hasClass("ui-icon-triangle-1-e")) {
$this.removeClass("ui-icon ui-icon-triangle-1-e")
.addClass($.jgrid.icons.form.next);
} else if ($this.hasClass("ui-icon-disk")) {
$this.closest(".EditButton").css("float", "right");
$fmButton.html("<span class=\"" + $.jgrid.icons.form.save + "\"></span><span>" + $fmButton.text() + "</span>");
} else if ($this.hasClass("ui-icon-close")) {
$this.closest(".EditButton").css("float", "right");
$fmButton.removeClass("fm-button-icon-left")
.addClass("fm-button-icon-right")
.html("<span class=\"" + $.jgrid.icons.form.undo + "\"></span><span>" + $fmButton.text() + "</span>");
}
});
}).bind("jqGridHeaderClick", function (e, gridstate) {
var $icon;
if (this.p.fontAwesomeIcons) {
$icon = $(this).closest(".ui-jqgrid").find(".ui-jqgrid-titlebar>.ui-jqgrid-titlebar-close>span");
if (gridstate === "visible") {
$icon.removeClass("ui-icon ui-icon-circle-triangle-n fa-arrow-circle-down")
.addClass($.jgrid.icons.titleVisibleGrid).parent().addClass($.jgrid.icons.titleIcon);
} else if (gridstate === "hidden") {
$icon.removeClass("ui-icon ui-icon-circle-triangle-n fa-arrow-circle-up")
.addClass($.jgrid.icons.titleHiddenGrid).parent().addClass($.jgrid.icons.titleIcon);
}
}
}).bind("jqGridInitGrid", function () {
var $this = $(this), $pager, $sortables;
if (this.p.fontAwesomeIcons) {
$pager = $this.closest(".ui-jqgrid").find(".ui-pg-table");
$pager.find(".ui-pg-button>span.ui-icon-seek-first")
.removeClass("ui-icon ui-icon-seek-first")
.addClass($.jgrid.icons.pager.first);
$pager.find(".ui-pg-button>span.ui-icon-seek-prev")
.removeClass("ui-icon ui-icon-seek-prev")
.addClass($.jgrid.icons.pager.prev);
$pager.find(".ui-pg-button>span.ui-icon-seek-next")
.removeClass("ui-icon ui-icon-seek-next")
.addClass($.jgrid.icons.pager.next);
$pager.find(".ui-pg-button>span.ui-icon-seek-end")
.removeClass("ui-icon ui-icon-seek-end")
.addClass($.jgrid.icons.pager.last);
$this.closest(".ui-jqgrid")
.find(".ui-jqgrid-titlebar>.ui-jqgrid-titlebar-close>.ui-icon-circle-triangle-n")
.removeClass("ui-icon ui-icon-circle-triangle-n")
.addClass("fa fa-arrow-circle-up").parent().addClass("ui-corner-all fa-title");
$sortables = $this.closest(".ui-jqgrid")
.find(".ui-jqgrid-htable .ui-jqgrid-labels .ui-jqgrid-sortable span.s-ico");
$sortables.find(">span.ui-icon-triangle-1-s")
.removeClass("ui-icon ui-icon-triangle-1-s")
.addClass("fa fa-sort-asc fa-lg");
$sortables.find(">span.ui-icon-triangle-1-n")
.removeClass("ui-icon ui-icon-triangle-1-n")
.addClass("fa fa-sort-desc fa-lg");
}
});
});
}
});
}(jQuery));
jQuery.jqGrid.checkboxFontAwesome4.js:
/*global jQuery */
(function ($) {
"use strict";
/*jslint unparam: true */
$.extend($.fn.fmatter, {
checkboxFontAwesome4: function (cellValue, options) {
var title = options.colModel.title !== false ? ' title="' + (options.colName || options.colModel.label || options.colModel.name) + '"' : '';
return (cellValue === 1 || String(cellValue) === "1" || cellValue === true || String(cellValue).toLowerCase() === "true") ?
'<i class="fa fa-check-square-o fa-lg"' + title + '></i>' :
'<i class="fa fa-square-o fa-lg"' + title + '></i>';
}
});
$.extend($.fn.fmatter.checkboxFontAwesome4, {
unformat: function (cellValue, options, elem) {
var cbv = (options.colModel.editoptions) ? options.colModel.editoptions.value.split(":") : ["Yes", "No"];
return $(">i", elem).hasClass("fa-check-square-o") ? cbv[0] : cbv[1];
}
});
}(jQuery));
UPDATED: Another demo contains some additional CSS styles which improve visibility of jqGrid if one includes bootstrap.css of the Bootstrap 3.0.2. I am sure that the styles are not the best, but there fix the problems which I found in my tests. Below are the styles:
.ui-jqgrid .ui-pg-table .ui-pg-input, .ui-jqgrid .ui-pg-table .ui-pg-selbox {
height: auto;
width: auto;
line-height: inherit;
}
.ui-jqgrid .ui-pg-table .ui-pg-selbox {
padding: 1px;
}
.ui-jqgrid { line-height: normal; }
div.ui-jqgrid-view table.ui-jqgrid-btable {
border-style: none;
border-top-style: none;
border-collapse: separate;
}
.ui-jqgrid .ui-jqgrid-titlebar-close.fa-title {
border-collapse: separate;
margin-top: 0;
top: 0;
margin-right: 2px;
height: 22px;
width: 20px;
padding: 2px;
}
.ui-jqgrid .ui-jqgrid-titlebar-close.fa-title.ui-state-hover span {
margin-top: -1px;
margin-left: -1px;
}
.ui-paging-info { display: inline; }
.ui-jqgrid .ui-pg-table { border-collapse: separate; }
div.ui-jqgrid-view table.ui-jqgrid-btable td {
border-left-style: none
}
div.ui-jqgrid-view table.ui-jqgrid-htable {
border-style: none;
border-top-style: none;
border-collapse: separate;
}
div.ui-jqgrid-view table.ui-jqgrid-btable th {
border-left-style: none
}
.ui-jqgrid .ui-jqgrid-htable th div {
height: 14px;
}
.ui-jqgrid .ui-jqgrid-resize {
height: 18px !important;
}
UPDATED 2: One more demo works with Font Awesome 4.2 and Bootstrap 3.2. The usage is very easy. One should include some .css (jQuery.jqGrid.fontAwesome4.css and jQuery.jqGrid.bootstrap-fixes.css) and .js files (jQuery.jqGrid.fontAwesome4.js and jQuery.jqGrid.checkboxFontAwesome4.js) and to use .jqGrid("initFontAwesome") before the grid are created. To fix problems with height of editing form at the second opening I used beforeInitData: function () { $("#editmod" + this.id).remove(); } additionally. One can download the latest versions of jQuery.jqGrid.fontAwesome4.css, jQuery.jqGrid.bootstrap-fixes.css, jQuery.jqGrid.fontAwesome4.js and jQuery.jqGrid.checkboxFontAwesome4.js from here.
For custom buttons, here is quick and... work around:
$(grid).jqGrid('navButtonAdd', pager, {
buttonicon: 'none',
caption: '<span class="my-fa-icon fa fa-barcode"></span> My Caption Here',
id: 'btnMyButton'
})
if you need to change the caption dynamically, update the div (representing the button) html (not text):
var myButton = $($(grid)[0].p.pager + '_left ' + 'td#btnMyButton');
$(myButton ).html('<span class="my-fa-icon fa fa-barcode"></span> My NEW Caption Here');
I included a css class, .my-fa-icon, just in case you want to add some customization (and make the display closer to what jqGrid does) - for example, you can add this to your css file:
.ui-jqgrid .ui-jqgrid-pager .ui-pg-button .ui-pg-div span.my-fa-icon { margin: 0 2px; width: 1.4em; font-size: 1.4em; float: left; overflow: hidden; }

jquery ui sortables connect lists: copy items

I have two lists, I want both of them to be sortable and want to be able to copy (drag) items from list1 to list2 and vice versa.
http://jqueryui.com/demos/sortable/#connect-lists
Is what I want, but the items are moved, not copied.
I did a few experiments with draggables and droppables, but I can't get to to work keeping them sortable. For example: http://jsfiddle.net/tunafish/dvXmf/
OK here is my app; two lists of images, sortable and you can copy over from the connected list.
If an item already exists in the target it's disabled.
Hopefully useful to someone...
JSFiffle here: http://jsfiddle.net/tunafish/VBG5V/
CSS:
.page { width: 410px; padding: 20px; margin: 0 auto; background: darkgray; }
.album { list-style: none; overflow: hidden;
width: 410px; margin: 0; padding: 0; padding-top: 5px;
background: gray;
}
.listing { margin-bottom: 10px; }
.album li { float: left; outline: none;
width: 120px; height: 80px; margin: 0 0 5px 5px; padding: 5px;
background: #222222;
}
li.placeholder { background: lightgray; }
JS:
$("ul, li").disableSelection();
$(".album, .favorites").sortable({
connectWith: ".album, .favorites",
placeholder: "placeholder",
forcePlaceholderSize: true,
revert: 300,
helper: "clone",
stop: uiStop,
receive: uiReceive,
over: uiOver
});
$(".album li").mousedown(mStart);
var iSender, iTarget, iIndex, iId, iSrc, iCopy;
var overCount = 0;
/* everything starts here */
function mStart() {
// remove any remaining .copy classes
$(iSender + " li").removeClass("copy");
// set vars
if ($(this).parent().hasClass("listing")) { iSender = ".listing"; iTarget = ".favorites"; }
else { iSender = ".favorites"; iTarget = ".listing"; }
iIndex = $(this).index();
iId = $(this).attr("id");
iSrc = $(this).find("img").attr("src");
iCopy = $(iTarget + " li img[src*='" + iSrc + "']").length > 0; // boolean, true if there is allready a copy in the target list
// disable target if item is allready in there
if (iCopy) { $(iTarget).css("opacity","0.5").sortable("disable"); }
}
/* when sorting has stopped */
function uiStop(event, ui) {
// enable target
$(iTarget).css("opacity","1.0").sortable("enable");
// reset item vars
iSender = iTarget = iIndex = iId = iSrc = iCopy = undefined;
overCount = 0;
// reinit mousedown, live() did not work to disable
$(".album li").mousedown(mStart);
}
/* rolling over the receiver - over, out, over etc. */
function uiOver(event, ui) {
// only if item in not allready in the target
if (!iCopy) {
// counter for over/out (numbers even/uneven)
overCount++;
// if even [over], clone to original index in sender, show and fadein (sortables hides it)
if (overCount%2) {
if (iIndex == 0) { ui.item.clone().addClass("copy").attr("id", iId).prependTo(iSender).fadeIn("slow"); }
else { ui.item.clone().addClass("copy").attr("id", iId).insertAfter(iSender + " li:eq(" + iIndex + ")").fadeIn("slow"); }
}
// else uneven [out], remove copies
else { $(iSender + " li.copy").remove(); }
}
// else whoooooo
}
/* list transfers, fix ID's here */
function uiReceive(event, ui) {
(iTarget == ".favorites") ? liPrefix = "fli-" : liPrefix = "lli-";
// set ID with index for each matched element
$(iTarget + " li").each(function(index) {
$(this).attr("id", liPrefix + (index + 1)); // id's start from 1
});
}
HTML:
<div class="page">
<div class="container">
<h2>Photo Album</h2>
<ul class="listing album">
<li id="li-1"><img src="tn/001.jpg" /></li>
<li id="li-2"><img src="tn/002.jpg" /></li>
<li id="li-3"><img src="tn/003.jpg" /></li>
<li id="li-4"><img src="tn/004.jpg" /></li>
<li id="li-5"><img src="tn/005.jpg" /></li>
</ul>
</div>
<div style="clear:both;"></div>
<div class="container">
<h2>Favorites</h2>
<ul class="favorites album">
<li id="fli-1"><img src="tn/001.jpg" /></li>
<li id="fli-2"><img src="tn/002.jpg" /></li>
<li id="fli-3"><img src="tn/010.jpg" /></li>
</ul>
</div>
</div>
I have to say that FFish's answer to this has been incredibly helpful to me.
I'd make one suggestion; if the lists are being constantly changed the mousedown event seem to be called numerous times because of the re-registering of the event on all the child objects. It might be a bit of a kludge, but I've added an unbind first to ensure the mousedown event is only called once.
$(".album li").mousedown(mStart);
to
$(".album li").unbind('mousedown', mStart).mousedown(mStart);

Resources