How to remove the DOM elements of a modal on close - semantic-ui

Background: At time of writing, Fomantic-UI is the live-development fork of Semantic-UI which will one day be rolled into Semantic-UI and is for the mean time the de facto supported genus of Semantic-UI.
Issue: Fomantic-UI provides a modal capability - just call .modal() on a JQuery element et voila. However, when the modal is closed, the DOM elements for the modal element remain hidden in the DOM. My use case requires removal of those elements, but I am concerned about the 'left over' wiring for the modal capability.
Research done: The FUI documentation on modals is useful but is written from the perspective of getting a modal up, but not cleanly taking it down.
Reproduction: The snippet below is a code-based approach to creation of a modal and wiring for the button listeners. Click the button to open the modal, then click the close button. Two seconds later a simple JQuery count of DOM modal elements remaining will be shown - it should be zero but will be 1.
Notes:
1 - FUI modal has a frustrating feature of auto-closing the modal when any button is clicked. It has one saving clause which is that if the triggered button event handler returns false then the modal stays open. Clearly, if you are doing form validation etc, you need to know this. Additionally, if you prefer to override the default feature for all buttons, return false from the onHide function, e.g.
element.modal('setting', 'onHide', function(){ return false; });
element.modal('show');
2 - This snippet is using the the 'dist' versions of FUI. Since FUI changes often, the snippet may fail if there have been breaking changes by the time you see it. At time of writing the official release on jsdelivr cdn is 2.8.3. (Edited 17-Jan-2020).
var fnSpecial = function(){
console.log('Close button click function - return true to hide');
// ... do function activity here....
return true;
}
$('#b1').on('click', function(){
makeModal({
title: 'I be modal',
content: 'Modal be I !',
actions: [
{ text: 'Close', fn: fnSpecial}, // for more buttons in the modal add more entries here.
]
})
})
function makeModal(opts){
// create your modal element - I grab the modal template
var tplt = $('#modalTemplate').html();
// defaults for modal create
var obj = {
title: 'No title !',
content: 'No content',
actions: [
]
}
// Merge the above defaults with the user-supplied options
obj = $.extend(obj, opts);
// Apply modal options to the soon-to-be modal element
var ele = $(tplt);
ele.find('.modalHeading').html(obj.title);
ele.find('.modalBody').html(obj.content);
ele.addClass('modalContentCopy');
var modalButtons = ele.find('.modalButtons');
for (var i =0, max = obj.actions.length; i < max; i = i + 1 ){
var btn = $('<button >' + obj.actions[i].text + '</button>');
var fn = obj.actions[i].fn;
btn.data('fn', fn); // store the callback on the element to avoid closures.
btn.appendTo(modalButtons);
btn.on('click', function(e){
var fn = $(this).data('fn');
if (typeof fn === "function"){
var hide = fn(); // IMPORTANT: the function triggered for the button must return true to close the modal or false to keep it open !
console.log('Button says hide=' + hide)
if (hide){
ele.modal('destroy');
ele.modal('hide');
$('#info')
.html('***');
// wait 2 secs and see if the DOM element has gone
setTimeout( function(){
var num = $('.modalContentCopy').length;
$('#info')
.html(num)
.css({ backgroundColor: (num === 0 ? 'lime' : 'red')});
}, 2000);
}
}
});
}
// Simple example of how to reveal the modal element as a f-ui modal
ele
.appendTo($('body'))
.modal('setting', 'transition', "vertical flip")
.modal('setting', 'onHide', function(){ return false; }) // stop the auto-closing by FUI
.modal('show'); // finally show the modal
}
p {
padding: 10px;
}
.modalContent {
border: 1px solid lime;
margin: 5px;
padding: 5px;
}
.modalHeading {
border: 1px solid cyan;
margin: 5px;
padding: 5px;
}
.modalBody {
border: 1px solid magenta;
margin: 5px;
padding: 5px;
}
.modalContent {
background-color: white;
}
.ipt {
margin: 5px 20px;
display: block;
}
<link href="https://fomantic-ui.com/dist/semantic.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://fomantic-ui.com/dist/semantic.js"></script>
<body>
<p>Click the 'show modal' button to open a modal, then the 'Close' button to close it. See console for messages. The critical point is the final display of the modal artefact count which appears after a 2 second delay, allowing for transitions to complete. We want a zero - what will it be?
</p>
<p>
<button id='b1'>Show a modal</button> <span>Count of DOM artifacts after close should be zero - value is >>>>> </span><span id='info'>[value will appear here after modal close]</span>
</p>
<div id='modalTemplate' style='display: none;'>
<div class='modalContent'>
<div class='modalHeading'>
I am the heading
</div>
<div class='modalBody'>
I am the body
</div>
<div class='modalButtons'>
</div>
</div>
</div>
</body>

The answer is to use the currently undocumented features of modal('destroy') and modal('remove') after the closing animation completes.
.modal('setting', 'onVisible', function(){
ele
.on("animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd",
function(e){
console.log('Animation complete')
$(this).off(e); // remove this listener
$(this).modal('destroy'); // take down the modal object
$(this).remove(); // remove the modal element, at last.
});
})
If you do not observe the completion of the closing transition then FUI throws a warning about transitions on missing elements.
Modal.Destroy clears out the modal object that FUI attaches to the JQuery modal element. The ele.remove() is the standard JQuery remove function. The result is, as per my requirement, the element previously made modal is removed from the DOM.
There will be one artefact which is the FUI dimmer div, but my testing to date shows that this is not an issue, meaning they are not visible and do not rack up over time.
See snippet below for working example. Look at the end of the JS section for the solution.
var fnSpecial = function(){
console.log('Close button click function - return true to hide');
// ... do function activity here....
return true;
}
$('#b1').on('click', function(){
makeModal({
title: 'I be modal',
content: 'Modal be I !',
actions: [
{ text: 'Close', fn: fnSpecial}, // for more buttons in the modal add more entries here.
]
})
})
function makeModal(opts){
// create your modal element - I grab the modal template
var tplt = $('#modalTemplate').html();
// defaults for modal create
var obj = {
title: 'No title !',
content: 'No content',
actions: [
]
}
// Merge the above defaults with the user-supplied options
obj = $.extend(obj, opts);
// Apply modal options to the soon-to-be modal element
var ele = $(tplt);
ele.find('.modalHeading').html(obj.title);
ele.find('.modalBody').html(obj.content);
ele.addClass('modalContentCopy');
var modalButtons = ele.find('.modalButtons');
for (var i =0, max = obj.actions.length; i < max; i = i + 1 ){
var btn = $('<button >' + obj.actions[i].text + '</button>');
var fn = obj.actions[i].fn;
btn.data('fn', fn); // store the callback on the element to avoid closures.
btn.appendTo(modalButtons);
btn.on('click', function(e){
var fn = $(this).data('fn');
if (typeof fn === "function"){
var hide = fn(); // IMPORTANT: the function triggered for the button must return true to close the modal or false to keep it open !
console.log('Button says hide=' + hide)
if (hide){
ele.modal('destroy');
ele.modal('hide');
$('#info')
.html('***');
// wait 2 secs and see if the DOM element has gone
setTimeout( function(){
var num = $('#theBody').find('.modalContentCopy').length;
$('#info')
.html(num)
.css({ backgroundColor: (num === 0 ? 'lime' : 'red')});
}, 2000);
}
}
});
}
// Simple example of how to reveal the modal element as a f-ui modal
ele
.appendTo($('body'))
.modal('setting', 'transition', "vertical flip")
.modal('setting', 'onHide', function(){ return false; }) // stop any button closing the modal
// <solution starts>
.modal('setting', 'onVisible', function(){
ele
.on("animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd",
function(e){
console.log('Animation complete')
$(this).off(e);
$(this).modal('destroy');
$(this).remove();
});
})
// <solution ends>
.modal('show'); // finally show the modal
}
p {
padding: 10px;
}
.modalContent {
border: 1px solid lime;
margin: 5px;
padding: 5px;
}
.modalHeading {
border: 1px solid cyan;
margin: 5px;
padding: 5px;
}
.modalBody {
border: 1px solid magenta;
margin: 5px;
padding: 5px;
}
.modalContent {
background-color: white;
}
.ipt {
margin: 5px 20px;
display: block;
}
<link href="https://fomantic-ui.com/dist/semantic.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://fomantic-ui.com/dist/semantic.js"></script>
<body id='theBody'>
<p>Click the 'show modal' button to open a modal, then the 'Close' button to close it. See console for messages. The critical point is the final display of the modal artefact count which appears after a 2 second delay, allowing for transitions to complete. We want a zero - what will it be?
</p>
<p>
<button id='b1'>Show a modal</button> <span>Count of DOM artifacts after close should be zero - value is >>>>> </span><span id='info'>[value will appear here after modal close]</span>
</p>
<div id='modalTemplate' style='display: none;'>
<div class='modalContent'>
<div class='modalHeading'>
I am the heading
</div>
<div class='modalBody'>
I am the body
</div>
<div class='modalButtons'>
</div>
</div>
</div>
</body>

Related

Fire event at Infinite horizontal scroll end in ionic 5

I am trying to build infinite scrolling content horizontally. Following code does the job on the first load.
HTML:
<div class="thumnails">
<div class="list-thumbnail">
<div class="img-thumb" *ngFor="let user of userList">
<ion-grid>
<ion-row>
{{user.name}}
</ion-row>
</ion-grid>
</div>
</div>
</div>
SCSS:
.thumnails{
overflow-x: scroll;
overflow-y: hidden;
.list-thumbnail{
height: 100%;
white-space: nowrap;
.img-thumb{
display: inline-block;
border: 1px solid #ddd;
border-radius: 4px;
padding: 3px;
}
}
}
::-webkit-scrollbar {
display: none;
}
After this, I need to fire a method when a user reaches the end on scroll. Ionic has the component ion-infinite-scroll which works in vertical scroll but it's not firing when I use it with the above code.
Is there a way to fire an event at the end of the horizontal scroll?
I suggest trying the ionScroll event from ion-content component.
Enable ionScroll like:
<ion-content [scrollEvents]="true" (ionScroll)="logScrolling($event)">
On logScrolling check to see if the scroll reached a certain point. If it did, add more data to your infinite scrolling content:
async logScrolling($event) {
if($event.target.localName != "ion-content") {
return;
}
const scrollElement = await $event.target.getScrollElement();
const scrollWidth = scrollElement.scrollWidth - scrollElement.clientWidth;
const threshold = 50;
if(scrollWidth < threshold) {
//...load data here
}}
I did not test it but it should work like that. Please let me know if you ran into any problems.
Here is my solution. Is not beautiful and I did not test it for performance issues but it does exactly what you want
HTML:
<ion-content>
<div class="thumnails" (scroll)="scrollEv($event)">
<div class="list-thumbnail">
<div class="img-thumb" *ngFor="let user of userList">
<span class="id" hidden>{{ user.id }}</span>
<ion-grid>
<ion-row>
{{ user.name }}
</ion-row>
</ion-grid>
</div>
</div>
</div>
</ion-content>
TS:
userList: Array<any> = [];
// elements count till the end of list to trigger the `getData()` function
elementsToTrigger: number = 10;
constructor() { }
ngOnInit() {
this.getData();
}
scrollEv(ev) {
// get the last visible on screen element of the list by it's coordenates:
// x => the offsetWidth of the body - 5px so it doesn't get out of boundaries
// y => distance the list is from the top. Note that here the list is fixed at the top because it's the only element on screen, but if, at some point,
// it will scroll or be placed at someplace else you should get this distance manually before this next line.
let elByCoords = document.elementFromPoint(document.body.offsetWidth - 5, 0);
let elId = elByCoords.children[0].textContent;
// check if the last element visible on screen is the "elementsToTrigger'th to last" and if so get more data
// here you should also check if all data has been loaded so you don't keep sending requests to your backend
if ((this.userList.length - this.userList.findIndex(user => user.id === +elId)) <= this.elementsToTrigger) {
this.getData();
}
}
getData() {
// here goes your logic to retrieve more results from database.
let startAt: number = this.userList?.length || 0;
let arr = [];
for (let i = startAt; i < startAt + 50; i++) {
arr.push({ id: i, name: `User ${i}` });
}
this.userList = (this.userList || []).concat(arr);
}
You may need to play a little with document.elementFromPoint(x, y) to find the correct values

jQuery UI widget CSS inhertiance issues

I have a couple different types of UI widgets on my page. There is a class that is common between all of them .ui-widget-content. I need to style this class differently for each one, so I have assigned unique ids or classes to the HTML elements. This worked ok for one type of widget (dialog where I can use the dialogClass option in the JS to assign classes), but the other type of widget (slider) will still only inherit styles from .ui-widget-content even when I specify a style for #id .ui-widget-content to get at the specific element of interest. I'm kind of at a loss on how to override the original style at this point.
HTML:
<div id="opacitySlide" class="slider">
<div id="opacityVal" class="ui-slider-handle"></div>
</div>
<div id="habClassify-dialog" title="Habitat Classification">
<div id="HabClassifyGPService">
//whole bunch of stuff
</div>
</div>
<div id="error-dialog" title="ERROR"></div>
<div id="success-dialog" title="SUCCESS">
<p>Habitat classification completed successfully! Your results will be viewable in 10 minutes.</p>
</div>
CSS:
//This one doesn't work and get overridden by the default style .ui-widget-content
#opacitySlide .ui-widget-content {
border: 1px solid black;
}
//This one does work, these classes are assigned in the JS, NOT the HTML
.habClassify-dialog .ui-widget-content,
.error-dialog .ui-widget-content,
.success-dialog .ui-widget-content {
border: none;
}
I've also attempted to use the custom class I assigned in the HTML instead of the id for the non-working CSS as well, but no luck.
.slider .ui-widget-content {
border: 1px solid black;
}
Here's the JS code:
//Creates the popup dialog for the habitat classification button
var habClassifyDialog = $("#habClassify-dialog").dialog({
autoOpen: false,
height: "auto",
width: 400,
modal: true,
dialogClass: 'habClassify-dialog',
buttons: [{
id: "classify",
text: "Classify habitat",
click: upload
}],
close: function () {
$('#uploadForm')[0].reset();
$('#validation-text').empty();
}
});
$('#classifyHab').click(function() {
habClassifyDialog.dialog("open");
});
//Creates the popup dialog that contains error messages
var errorDialog = $("#error-dialog").dialog({
autoOpen: false,
height: "auto",
width: 1000,
modal: true,
dialogClass: 'error-dialog',
buttons: [{
id: "error-ok",
text: "Ok",
click: function () {
errorDialog.dialog("close");
}
}]
});
//Creates the popup dialog that shows the success message
var successDialog = $('#success-dialog').dialog({
autoOpen: false,
height: "auto",
width: 400,
modal: true,
dialogClass: 'success-dialog',
buttons: [{
id: "success-ok",
text: "Ok",
click: function () {
successDialog.dialog("close");
if (habClassifyDialog.dialog('isOpen')) {
habClassifyDialog.dialog("close");
}
}
}],
close: function () {
if (habClassifyDialog.dialog('isOpen')) {
habClassifyDialog.dialog("close");
}
}
});
//Create the opacity slider
var handle = $("#opacityVal");
$("#opacitySlide").slider({
range: "min",
value: 100,
min: 0,
max: 100,
create: function () {
handle.text($(this).slider("value") + "%");
},
slide: changeOpacity,
change: changeOpacity
});
If you haven't used jQuery UI before, it automatically adds a whole bunch of default styles to the widgets upon load, that's why you don't see class="ui-widget-content" in my HTML anywhere, it's not necessary to declare it.
Alright, I have officially made the stupidest mistake ever. Considering the HTML was generating with the correct custom ID I assigned, I figured there had to be a way to access the ui-widget-content class using that ID. It was as simple as chaining them together, before when I was testing this I had left a space in between.
Problem CSS (won't work):
#opacitySlide .ui-widget-content {
border: 1px solid black;
}
Simple fix (remove space between id and class):
#opacitySlide.ui-widget-content{
border: 1px solid black;
}
For the sake of completeness, the explanation is that if you leave a space between these items, it thinks the HTML is structured like this:
<div id="opacitySlide">
<div class="ui-widget-content"></div>
</div>
when really my HTML was structured like this:
<div id="opacitySlide" class="ui-widget-content"></div>

Vertical scroll inside paper-header-panel behaving inappropriately

I am currently working on a page. I have used polymer's paper-drawer-panel and paper-header-panel combination.
Basically there are two containers, the inner container needs to be scrolled horizontally while the outer container needs to be scrolled vertically. When I have not used the paper-header-panel, the behavior is appropriate. You can see the demo here. If you open this site on a mobile browser (chrome or others/haven't checked with Safari) or on the mobile simulator in chrome, I can scroll both horizontally as well as vertically. This is the behavior I have expected.
But, when I add in the paper-header-panel (part of my custom element <app-layout>), I am able to scroll horizontally (in a mobile browser) but when I touch an element present in the horizontally scrollable div and try to scroll it vertically as in the previous case, the vertical scroll doesn't work anymore. The demo is present here.
The relevant source code for the app-layout element is as below.
html -
<dom-module id="app-layout">
<link rel="import" type="css" href="app-layout.css">
<template>
<paper-drawer-panel id="drawerPanel" responsive-width="1024px" drawer-width="280px">
<paper-header-panel class="list-panel" drawer>
<!-- List Toolbar -->
<div class="paper-header has-shadow layout horizontal center" id="navheader">
</div>
<!-- Menu -->
<div class="left-drawer">
<paper-menu class="list" selected="0" on-iron-activate="_listTap">
<template is="dom-repeat" items="{{menus}}">
<paper-item role="menu"><iron-icon class="menuitems" icon$={{item.icon}}></iron-icon><span>{{item.label}}</span></paper-item>
</template>
</paper-menu>
</div>
</paper-header-panel>
<paper-header-panel class$="{{positionClass}}" main mode="{{mainMode}}">
<!-- Main Toolbar -->
<paper-toolbar class$="{{toolbarClass}}">
<paper-icon-button icon="menu" paper-drawer-toggle></paper-icon-button>
<div style="width:60px" id="app-image"><iron-image style="width:40px; height:40px; background-color: lightgray;"
sizing="contain" preload fade src= "/images/app-icon-110.png"></iron-image></div>
<div hidden$="{{_isMobile}}" class="flex">{{label}}</div>
<div class="flex"></div>
<paper-icon-button icon="search" on-tap="toggleSearch"></paper-icon-button>
<paper-icon-button icon="more-vert"></paper-icon-button>
</paper-toolbar>
<div class="content">
<paper-material>
<content select=".main-content"></content>
</paper-material>
</div>
</paper-header-panel>
</paper-drawer-panel>
</template>
</dom-module>
CSS -
* {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
:host {
width: 100%;
height: 100%;
-moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box;
display: block;
}
#drawerPanel {
--paper-drawer-panel-left-drawer-container: {
background-color: #eee;
};
}
paper-header-panel {
background-color: #eee;
}
paper-toolbar {
background-color: #00bcd4;
}
.left-drawer {
background-color: #eee;
}
paper-header-panel .content {
height: 100%;
}
paper-header-panel[mode=cover] .content {
padding: 0 90px 0 0px;
height: 100%;
}
paper-header-panel {
--paper-header-panel-cover-container: {
height: 100%;
left: 90px;
};
}
paper-header-panel[mode=cover] paper-toolbar {
color: #fff;
font-size: 20px;
font-weight: 400;
padding-right: 16px;
}
.paper-header-panel paper-toolbar #app-image {
margin-left: -15px;
}
paper-material {
overflow-y: auto;
height: auto;
background-color: #fff;
z-index: 1;
}
paper-header-panel[mode=cover] paper-material {
max-width: 1024px;
margin: 64px auto;
}
From our Comments,
As an alternative solution until you find the problem you can test using Iscroll 5 for the horizontal scroll of the cards and see if it works ok within the App.
Ive used Iscroll 5 for my App using both horizontal and vertical scrolls with 100's of Items and its fast on Polymer. I haven't had any performance issues so far although i turned off bounce in iscroll options eg ,bounce: false to get that extra performance boost
If you have Click events for the Cards then add ,click:true to the Iscroll options
Iscroll 5 guide here http://iscrolljs.com/
The demo creates the Iscroll for any row when the user scrolls horizontally there to save resources and the Original Demo in my comments was for JQM framework which has built in Swipe detection.
In Polymer version 0.5 it has built in Touch functionality not sure about version 1 yet but i never used yet https://www.polymer-project.org/0.5/docs/polymer/touch.html
For Polymer i created another Demo that Uses Javascript Touch events to detect horizontal movements only so you wont need any Other Touch gesture Pluggings to add to the App
Demo I set a 5sec delay for the code to initialize. Pretty much all the code is for handling Touch. In the function slide(x) { is the Iscroll code about 10 lines
http://codepen.io/anon/pen/pJRmLo
Code
var slides = 17; //how many items in a row
var totalwidth = slides * 80; //times that by the width of each item in a row
$(".scroller").css("width", totalwidth+"px"); //set the total width of the horizontal wrapper
// touch function
var startPos;
var handlingTouch = false;
var itemis;
setTimeout(function() {
document.addEventListener('touchstart', function(e) {
// Is this the first finger going down?
if (e.touches.length == e.changedTouches.length) {
startPos = {
x: e.touches[0].clientX,
y: e.touches[0].clientY
};
}
});
document.addEventListener('touchmove', function(e) {
// If this is the first movement event in a sequence:
if (startPos) {
// Is the axis of movement horizontal?
if (Math.abs(e.changedTouches[0].clientX - startPos.x) > Math.abs(e.changedTouches[0].clientY - startPos.y)) {
handlingTouch = true;
e.preventDefault();
onSwipeStart(e);
}
startPos = undefined;
} else if (handlingTouch) {
e.preventDefault();
onSwipeMove(e);
}
});
document.addEventListener('touchend', function(e) {
if (handlingTouch && e.touches.length == 0) {
e.preventDefault();
onSwipeEnd(e);
handlingTouch = false;
}
});
function slide(x) {
var cclass = $(itemis).attr("class")
var ccclass = "."+cclass;
var newis = $(itemis).attr("data-id");
if (newis != "running") {
var cclass = new IScroll(ccclass, {
eventPassthrough: true,
scrollX: true,
scrollY: false,
preventDefault: false
});
cclass.scrollBy(-50, 0, 500);
//control here how many pixels to auto scroll uppon activating the scroll eg -50px
$(itemis).attr("data-id","running")
}
}
var swipeOrigin, x, itempos;
function onSwipeStart(e) {
// find what element is been touched. In your case it may be closest("swElement") but you need to test
itemis = $(e.target).closest("div");
// when touching over an element get its target, in this case the closest div of the row
swipeOrigin = e.touches[0].clientX;
}
function onSwipeMove(e) {
x = e.touches[0].clientX - swipeOrigin;
// slide(x);
}
function onSwipeEnd(e) {
//On Touch End if x (distance traveled to the right) is greater than +35 pixels then slide element +100 pixels.
if (x > 35) {
slide(0);
}
else {
slide(0);
}
}
}, 5000);
The above touch function I originally used for transforming/moving list items by touch drag similarly to Gmail App, for JQM and Polymer list items. Can be used for anything horizontally in the case of Iscroll is not really used in that way but it basically says if you touch move horizontally over a row activate the Iscroll for that row
Check my demo in the Link for an alternative use of the function.
jQuery touchSwipe event on element prevents scroll
The problem arises because on mobiles, horizontal scrolling is disabled when vertical scrolling is active and vice-versa. I have solved this for now using Tasos suggestion to use iScroll. I used polymer's async function to initialize the scrollers with a delay. Here is the code I have used as part of polymer-ready.
<script>
Polymer ({
...
...
ready:function() {
this.async(function() {
this.setScroll();
}, null, 300);
},
setScroll: function() {
var nodeList = document.querySelectorAll('.wrapper');
if(nodeList.length == 0) {
this.async(function() { this.setScroll(); }, null, 300);
}
for (var i=0; i<nodeList.length; i++) {
// this.setScrollDirection ('y', nodeList[i]);
nodeList[i].id = 'wrapper'+i;
var myScroll = new IScroll('#'+nodeList[i].id, { eventPassthrough: true, scrollX: true, scrollY: false, preventDefault: false });
// myScroll.scrollBy(-50, 0, 500);
}
}
})();
</script>
I was also able to use Polymers track gesture as mentioned in the documentation.
Listening for certain gestures controls the scrolling direction for
touch input. For example, nodes with a listener for the track event
will prevent scrolling by default. Elements can be override scroll
direction with this.setScrollDirection(direction, node), where
direction is one of 'x', 'y', 'none', or 'all', and node defaults to
this.
state - a string indicating the tracking state:
start - fired when tracking is first detected (finger/button down and moved past a pre-set distance threshold)
track - fired while tracking
end - fired when tracking ends
x - clientX coordinate for event
y - clientY coordinate for event
dx - change in pixels horizontally since the first track event
dy - change in pixels vertically since the first track event
ddx - change in pixels horizontally since last track event
ddy - change in pixels vertically since last track event
hover() - a function that may be called to determine the element currently being hovered
I have used (dx > dy) to understand whether it is a horizontal or vertical swipe and then enabled horizontal and vertical swiping specifically as per the case. This worked but I liked the bounce and the other options provided by iScroll and besides its only 4KB minified and gzipped. So, I decided to go with iScroll.
[Note: jQuery is not needed to use iScroll. It is an independent script]

How to highlight div on click

I would like to highlight a div when it's clicked.
Heres the example: www.spidex.org
On this website if you hover any of the navigation buttons a div on the top of the page is highlighted.
You may use jQuery for achieving this.
get jQuery here.
now consider that you have a div that you want to highlight on mouseover called item.
do this by adding an overlay div.
div.overlay{
opacity:0;
background:#000;
width:100%;
height:100%;
position:absolute;
top:50px;left:0;
}
then use jquery
jQuery(document).ready(function($){
$('.item').mouseover(function(){
$('.overlay').css({opacity:0.3});
});
});
You can change the appearance of elements when hovered using the :hover pseudo-class.
For example
div:hover {
color: red;
}
Secondly, you can change the text color via using the color property and the background color using the background-color property.
Both are shown below:
div:hover {
color: black;
background-color: white;
}
In your given example, when you hover over the primary navigation items in the super-header, then the body dims. I agree with your analysis that this is managed with some cover div of the body.
One cross-browser approach (using jQuery in this example) you might consider would be the following:
EXAMPLE HTML:
<div class="header">
Some Link
</div>
<div class="body">
<div class="body-content">
[ CONTENT HTML ]
</div>
<div class="body-cover"></div>
</div>
EXAMPLE CSS:
.body {
position: relative; /* container needs position */
}
.body-cover {
position: absolute;
top: 0px;
left: 0px;
background-color: blue;
/*
you could use a sligtly transparent background here,
or tween your opacity in your javascript
*/
}
EXAMPLE JavaScript:
// on dom ready
jQuery(function ($) {
// closures
var $links = $('.header a');
var $body = $('.body');
var $content = $body.find('.body-content');
var $cover = $body.find('.body-cover');
var sCoverHiddenCssClassName = 'body-cover-hidden';
var sCoverTweeningCssClassName = 'body-cover-tweening';
var sCoverShowingCssClassName = 'body-cover-showing';
// closure methods
var fMouseOver = function () {
// check to see if hidden (not already tweening or showing)
if ($cover.hasClass(sCoverHiddenCssClassName)) {
// check content, may have changed.
$cover.css({
height: $content.outerHeight(),
width: $content.outerWidth()
});
// animate or tween cover (do this however you want)
$cover
.removeClass(sCoverHiddenCssClassName)
.addClass(sCoverTweeningCssClassName)
.fadeIn(function () {
// when completed, mark as showing/visible
$cover
.removeClass(sCoverTweeningCssClassName)
.addClass(sCoverShowingCssClassName);
});
}
};
var fMouseOut = function () {
// check to see if visible (not already tweening or hidden)
if ($cover.hasClass(sCoverShowingCssClassName)) {
// animate or tween cover (do this however you want)
$cover
.removeClass(sCoverShowingCssClassName)
.addClass(sCoverTweeningCssClassName)
.fadeOut(function () {
// when completed, mark as showing/visible
$cover
.removeClass(sCoverTweeningCssClassName)
.addClass(sCoverHiddenCssClassName);
});
}
};
var fClick = function (e) {
// prevent default if needed for anchors or submit buttons
// e.preventDefault();
if ($cover.hasClass(sCoverHiddenCssClassName)) {
fMouseOver();
}
else if ($cover.hasClass(sCoverShowingCssClassName)) {
fMouseOut();
}
};
// init interaction
$cover.hide().addClass(sCoverHiddenCssClassName);
$links.each(function () {
// wire links
jQuery(this)
.mouseover(fMouseOver)
.mouseout(fMouseOut);//
//.click(fClick); // use click event if desired
});
});
JQuery UI is also gives an good option to quickly highlight div .
https://jqueryui.com/effect/
$( "#divId" ).effect( "highlight", 500 );

How can i position a dropdown at cursor position inside a textarea?

How can i position my dropdown at cursor position inside a textarea? I have found this question was already asked here many times but i cant able figure out the correct solution ..
this is the JSBIN
please help me with your suggestions
Thanks in advance
I know it isn't an exact answer on the question (this solution doesn't use a textarea, but a contentEditable div), but I don't think there is any way of getting x-y-coordinates using either the event, an attribute or function on the textarea or an attribute or function on the Selection object.
I have meshed up an example on JSBin. Please note that I haven't bothered testing for compatibility in other browsers and that it won't return the caret to where you left off. I can't figure out the code for that. I believe window.getSelection() will not work in IE, and in IE8- it would be completely different. You probably want to make sure too, that the menu will not be displayed right from the edge of the screen.
The HTML
<div id="target" contentEditable="true">Type # to see the dropdown.... </div>
<div class="dropdown">
<ul id="dropdown" class="dropdown-menu hide" role="menu" aria-labelledby="dropdownMenu">
<li><a>One</a> </li>
<li><a>Two</a></li>
<li><a>Three</a></li>
<li><a>Four</a> </li>
</ul>
</div>
The CSS
#target {
height: 100px;
border: 1px solid black;
margin-top: 50px;
}
#dummy {
display: inline-block;
}
.dropdown {
position: absolute;
top: 0;
left: 0;
}
The Javascript & JQuery
$("#target").keydown( function(e) {
if(e.which === 50 && e.shiftKey === true ) {
//Prevent this event from actually typing the #
e.preventDefault();
//console.log( window.getSelection() );
var sel = window.getSelection();
var offset = sel.baseOffset;
var node = sel.focusNode.parentNode;
//Get the text before and after the caret
var firsttext = node.innerHTML.substr(0,sel.baseOffset);
var nexttext = (sel.baseOffset != sel.focusNode.length ) ? node.innerHTML.substr( sel.baseOffset, sel.focusNode.length) : "";
//Add in # + dummy, because # is not in there yet on keydown
node.innerHTML = firsttext + '#<div id="dummy"></div>' + nexttext;
//Transfer all relevant data to the dropdown menu
$('.dropdown').css('left', $('#dummy')[0].offsetLeft).css('top', $('#dummy')[0].offsetTop).prop('x-custom-offset', offset + 1);
//Delete the dummy to keep it clean
//This will split the contents into two text nodes, which we don't want
//$('#dummy').remove();
node.innerHTML = firsttext + '#' + nexttext;
//Put the caret back in place where we left off
//...I can't seem to figure out how to correctly set the range correctly...
$('#dropdown').removeClass('hide').addClass('show');
} else {
$('#dropdown').removeClass('show').addClass('hide');
$('.dropdown').removeProp('x-custom-offset');
}
});
$('#dropdown').on( 'click', 'li a', function( e ) {
e.preventDefault();
$('#target').html( function( i, oldtext ) {
var firsttext = oldtext.substr( 0, $('.dropdown').prop('x-custom-offset') );
var nexttext = oldtext.substr( $('.dropdown').prop('x-custom-offset'), oldtext.length );
console.log( e );
var inserttext = e.target.innerText;
//Cleanup
$('#dropdown').removeClass('show').addClass('hide');
return firsttext + inserttext + nexttext;
} );
} );
The explanation
This example works based on that you can insert an element in a contentEditable and retrieve it's offset to the top and the left of the screen. When shift + key 50 is pressed, the event handler will prevent the # from being written and instead inserts the # + dummy object itself. Then we retrieve the offset from this object and move the dropdown menu to that offset. Furthermore, we save the character-offset as a custom property x-custom-offset of the menu, so that we can insert a value at that specific location. We then need to remove the dummy div, but if we would remove the dummy with $('#dummy').remove() the text node before the dummy and the text node behind the dummy will not merge. This will delete the last textnode if we were to put an other # somewhere and/or place it in the wrong location. Therefore, we simply replace the contents of the editable div again. Last, the caret must be set back to it's original position. I cannot figure out how to do this properly though.
The second handler is to insert text into the textbox. The code should be self-explanatory. The x-custom-offset property we set earlier is used here to insert the text into the correct place in the textbox. $('#dropdown').on( 'click', 'li a', function( e ) { ... } ); will attach the click event to the ul instead of the li's, so that it will keep working if you dynamically create the li's (but it will only fire if you click the link part).
You can get the position of the mouse and then move the drop-down list to this position.
You just need to ensure the popup content has a higher z-index than the element you'd like it occlude, and that it's position is set to absolute.
Here's a small test sample I wrote once.
<!DOCTYPE html>
<html>
<head>
<script>
function byId(e){return document.getElementById(e);}
function newEl(tag){return document.createElement(tag);}
function newTxt(txt){return document.createTextNode(txt);}
function toggleClass(element, newStr)
{
index=element.className.indexOf(newStr);
if ( index == -1)
element.className += ' '+newStr;
else
{
if (index != 0)
newStr = ' '+newStr;
element.className = element.className.replace(newStr, '');
}
}
function forEachNode(nodeList, func)
{
var i, n = nodeList.length;
for (i=0; i<n; i++)
{
func(nodeList[i], i, nodeList);
}
}
window.addEventListener('load', mInit, false);
function mInit()
{
}
function onShowBtn(e)
{
var element = byId('popup');
element.className = element.className.replace(' hidden', '');
var str = '';//'border-radius: 32px; border: solid 5px;';
e = e||event;
str += "left: " + e.pageX + "px; top:"+e.pageY+"px;"
element.setAttribute('style',str);
}
function onHideBtn()
{
var element = byId('popup');
if (element.className.indexOf(' hidden') == -1)
element.className += ' hidden';
}
</script>
<style>
#controls
{
display: inline-block;
padding: 16px;
border-radius: 6px;
border: solid 1px #555;
background: #AAA;
}
#popup
{
border: solid 1px #777;
border-radius: 4px;
padding: 12px;
background: #DDD;
display: inline-block;
z-index: 2;
position: absolute;
}
#popup.hidden
{
visibility: hidden;
}
</style>
</head>
<body>
<div id='controls'>
<input type='button' value='show' onclick='onShowBtn()'>
<input type='button' value='hide' onclick='onHideBtn()'>
</div>
<br>
<div id='popup'>
<p>This is some assorted
text</p>
<hr>
<ul>
<li>item a</li>
<li>item 2</li>
<li>item iii</li>
</ul>
</div>
</body>
</html>

Resources