How to scrollTop a div whose content is managed by AngularDart? - scroll

I have a div holding some chat history. I would like for the div content to scroll when it becomes full. I have this working using jQuery in another project, but what would be the proper AngularDart way to achieve this?
Simplifying a bit, in my HTML I have
<div class="chatHistory">{{ctrl.text}}</div>
(using style white-space:pre) and in my controller I have the method
void addToChatHistory(String msg) {
text += msg;
var elt = querySelector(".chatHistory");
elt.scrollTop = elt.scrollHeight;
}
Issues:
This code scrolls the div content but not quite enough because it sets the scrollTop too quickly; i.e., even before the .chatHistory can be updated by Angular.
Besides this (partial) solution doesn't feel very Angular in style given the use of querySelector.
I tried setting up a watch on the text field but that also fires too early. Suggestions?

For the first issue you can use Timer.run to defer the scroll execution :
Timer.run(() => elt.scrollTop = elt.scrollHeight);
For the second issue you can inject the Element managed by your controller and use querySelector on it :
MyController(Element e) : elt = e.querySelector('.chatHistory');

EDIT
I published a package containing this directive: http://pub.dartlang.org/packages/bwu_angular
-------
I would create a Directive/Decorator like NgEventDirective but for the resize event and add it to your div. In the event handler you set your scrollTop property.
I found contradictory info about the resize event.
DART - Resize div element event says it is available everywhere
Onresize for div elements? is a workaround for browsers that don't support this event.
another page covering this topic: http://marcj.github.io/css-element-queries/
I tried to create an Angular implementation for the 'workaround' (2nd link) but run into this issue https://code.google.com/p/dart/issues/detail?id=18062 which contains info about a workaround but I didn't yet find time to implement it.
EDIT
I checked my attempt and it worked in Dartium with the version I downloaded today (Dart VM version: 1.4.0-dev.4.0 (Thu May 1 04:06:09 2014) on "linux_x64").
I haven't tried in other version since I created the issue.
(The code should be improved to make the directive more generic - the event method should be assignable by an attribute not hardcoded)
index.html
<!DOCTYPE html>
<html ng-app>
<head>
<script src="packages/web_components/platform.js"></script>
<style>
.resize-triggers {
visibility: hidden;
}
.resize-triggers, .resize-triggers > div, .contract-trigger:before {
content: " ";
display: block;
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
overflow: hidden;
}
.resize-triggers > div {
background: #eee;
overflow: auto;
}
.contract-trigger:before {
width: 200%;
height: 200%;
}
</style>
<style>
#my_element {
height: 200px;
overflow: scroll;
}
</style>
</head>
<body ng-cloak>
<div>
<div id="my_element" style="border: 1px solid blue;">
<div id='my_sizable' ng-observe-size></div>
</div>
</div>
<script type="application/dart" src="index.dart"></script>
<script type="text/javascript" src="packages/browser/dart.js"></script>
</body>
</html>
index.dart
library angular_observe_resize.main;
import 'dart:async' as async;
import 'dart:html' as dom;
import 'package:angular/angular.dart' as ng;
import 'package:angular/application_factory.dart' as ngaf;
// see https://stackoverflow.com/questions/19329530
// and this bug https://code.google.com/p/dart/issues/detail?id=18062
// source from http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/
#ng.Decorator(selector: '[ng-observe-size]')
class NgObserveSizeDirective implements ng.AttachAware, ng.DetachAware {
dom.Element _element;
bool _hasAttacheEvent;
NgObserveSizeDirective(this._element);
void onSizeChange(dom.Event e) {
_element.parent.scrollTop = _element.scrollHeight;
}
dom.HtmlElement _triggers;
void resetTriggers() {
var expand = _triggers.children[0];
var contract = _triggers.children[_triggers.children.length - 1];
var expandChild = expand.children[0];
contract.scrollLeft = contract.scrollWidth;
contract.scrollTop = contract.scrollHeight;
expandChild.style.width = '${expand.offsetWidth + 1}px';
expandChild.style.height = '${expand.offsetHeight + 1}px';
expand.scrollLeft = expand.scrollWidth;
expand.scrollTop = expand.scrollHeight;
}
int _resizeLastWidth;
int _resizeLastHeight;
bool checkTriggers() {
return _element.offsetWidth != _resizeLastWidth ||
_element.offsetHeight != _resizeLastHeight;
}
int _resizeRaf;
void scrollListener(dom.Event e) {
resetTriggers();
if(_resizeRaf != null) {
dom.window.cancelAnimationFrame(_resizeRaf);
}
_resizeRaf = dom.window.requestAnimationFrame((num highResTime){
if(checkTriggers()) {
_resizeLastWidth = _element.offsetWidth;
_resizeLastHeight = _element.offsetHeight;
onSizeChange(e);
}
});
}
#override
void attach() {
if(_element.getComputedStyle().position == 'static') {
_element.style.position = 'relative';
}
_triggers = new dom.DivElement()
..classes.add('resize-triggers')
..append(new dom.DivElement()..classes.add('expand-trigger')..append(new dom.DivElement()))
..append(new dom.DivElement()..classes.add('contract-trigger'));
_element.append(_triggers);
new async.Future.delayed(new Duration(seconds: 1), () {
//_triggers = _element.children[_element.children.length - 1];
resetTriggers();
dom.Element.scrollEvent.forTarget(_element, useCapture: true).listen(scrollListener);
});
}
#override
void detach() {
_triggers.remove();
}
}
class MyAppModule extends ng.Module {
MyAppModule() {
type(NgObserveSizeDirective);
}
}
main() {
print('main');
ngaf.applicationFactory().addModule(new MyAppModule()).run();
new async.Timer.periodic(new Duration(seconds: 1), (t) {
var elt = (dom.querySelector('#my_sizable') as dom.HtmlElement);
elt.append(new dom.Element.html('<div>${new DateTime.now()}</div>'));
});
}

If you're using an AngularDart component you can inject the shadowdom then select an element inside it to focus on. Didnt find a way of getting the container of the shadowdom.
new Future.delayed(new Duration(milliseconds: 300), () {
var rowElement = _shadowRoot.querySelector('.step-container');
if (rowElement != null ) {
rowElement.scrollIntoView(ScrollAlignment.TOP);
}
});
ShadowDom.host might work, though its currently marked as experimental.

Related

How to add navigation links (dots) to slick lightbox?

I have slick carousel and slick lightbox.
The latter has no dots or navigation links. How to add them?
Since lightbox using slick itself:
Add dots : true to dist/slick-lightbox.js (or rebuild coffeescript)
SlickLightbox.prototype.initSlick = function (index) {
/* Runs slick by default, using `options.slick` if provided. If `options.slick` is a function, it gets fired instead of us initializing slick. Merges in initialSlide option. */
var additional;
additional = { initialSlide: index, dots:true };
//...
}
Add styles to make them available
.slick-dots {
bottom: 0;
}
.slick-dots li button::before {
color: #9f9f9f;
}
.slick-dots li.slick-active button::before {
color: white;
}

Why this #import rule is not having any effect on this Web Component?

const sheet = new CSSStyleSheet()
sheet.replace(`
#import url('https://fonts.googleapis.com/css?family=Roboto&display=swap');
p {
color: red;
font-family: 'Roboto';
}
`)
customElements.define('my-component',
class extends HTMLElement {
constructor() {
super()
const shadowRoot = this.attachShadow({
mode: 'open'
})
shadowRoot.adoptedStyleSheets = [sheet]
shadowRoot.innerHTML = '<p>my component</p>'
}
}
)
Check here: https://jsfiddle.net/jdvivar/h1x2vs3u/
A custom tag my-component is created, a shadow root attached and an adopted stylesheet is copied inside. I think it should render the content with the correct font, but it does not. Only the color style is applied. Do you know why?

Jquery Mobile + Phonegap improve listview performance

In my JQM 1.4 + Phonegap 3.6 app, I am using a listview as in the following code.
HTML:
<div id="boardselection">
<ul id="modelsListview" data-role="listview" data-icon="false">
</ul>
</div>
JS:
function resetModelsListView(prodata, firsttime, funfeatureOn, specificBrand, specificPro) {
console.log("on passe dans resetModelsListView");
// funfeatureOn = 0;
//debug timer
var time = [];
var dummy;
var i;
var listviewdeferred = $.Deferred();
var optionspro = '';
var optionsbrand = '';
var optionsmodel = '';
var countpros = 0;
var countbrands = 0;
var countmodels = 0;
var chosenmodelListViewHandle = $('#modelsListview');
var chosenbrandSelect = $('#chosenbrand');
optionsmodel += '';
var alreadyusedbrands = [];
prodata.sort(SortByName);
// get previously selected model to reselect it later
//var previouslySelectedModelId =parseInt(chosenmodelSelect.find('li:selected').val());
if (!funfeatureOn) {
prodata.sort(SortByModel);
} else {
prodata.sort(SortByFUN);
}
//populate model list
//~ if (firsttime){
//~ var perfIsChecked = true;
//~ var smallwaveIsChecked = true;
//~ var stepupIsChecked = true;
//~ }else {
var perfIsChecked = $('#checkboxperf').is(":checked");
var smallwaveIsChecked = $('#checkboxsmallwave').is(":checked");
var stepupIsChecked = $('#checkboxstepup').is(":checked");
//~ }
console.log("perfIsChecked, smallwaveIsChecked, stepupIsChecked =");
console.log(perfIsChecked);
console.log(smallwaveIsChecked);
console.log(stepupIsChecked);
//if none checked then no filter
if (!perfIsChecked && !smallwaveIsChecked && !stepupIsChecked) {
perfIsChecked = true;
smallwaveIsChecked = true;
stepupIsChecked = true;
}
for (i = 1; i < prodata.length; ++i) {
if (specificBrand && prodata[i]['brand'] != specificBrand) {
} else if (specificPro && prodata[i]['name'] != specificPro) {
} else {
if (prodata[i]['fun'] == 0 && perfIsChecked) {
optionsmodel += '<li><a class="optionfuninit" href="#" data-proid="' + prodata[i]['id'] + '"><div class="listviewtexts"><span class="listviewtextsmodel">' + prodata[i]['model'] + '</span> - <span class="listviewtextspro">as surfed by ' + prodata[i]['name'] + '</span></div></a></li>';
} else if (prodata[i]['fun'] == 1 && smallwaveIsChecked) {
optionsmodel += '<li><a class="optionfuninit" href="#" data-proid="' + prodata[i]['id'] + '"><div class="listviewtexts"><span class="listviewtextsmodel">' + prodata[i]['model'] + '</span> - <span class="listviewtextspro">as surfed by ' + prodata[i]['name'] + '</span></div></a></li>';
} else if (prodata[i]['fun'] == 2 && stepupIsChecked) {
optionsmodel += '<li><a class="optionstepupinit" href="#" data-proid="' + prodata[i]['id'] + '"><div class="listviewtexts"><span class="listviewtextsmodel">' + prodata[i]['model'] + '</span> - <span class="listviewtextspro">as surfed by ' + prodata[i]['name'] + '</span></div></a></li>';
} else if (prodata[i]['fun'] == 3 && smallwaveIsChecked) {
optionsmodel += '<li><a class="optionkidsinit" href="#" data-proid="' + prodata[i]['id'] + '"><div class="listviewtexts"><span class="listviewtextsmodel">' + prodata[i]['model'] + '</span> - <span class="listviewtextspro">as surfed by ' + prodata[i]['name'] + '</span></div></a></li>';
}
if (prodata[i]['model'] !== prodata[i - 1]['model']) { //eliminate name duplicates if prodata sorted by model
countmodels = countmodels + 1;
}
}
}
chosenmodelListViewHandle.html(optionsmodel);
if (chosenmodelListViewHandle.listview("option", "disabled")) {
chosenmodelListViewHandle.listview("option", "disabled", false);
}
//~ if (resetModelsOnly) {
//~ if ( !isNaN(previouslySelectedModelId) ) {
//~ chosenmodelListViewHandle.find('li[href="' + previouslySelectedModelId + '"]').attr("selected", "selected").siblings('li').removeAttr('selected');
//~ }
//~ }
//~ highlightFunModels(funfeatureOn, 1);
//~ highlightStepupModels(funfeatureOn, 0);
chosenmodelListViewHandle.listview("refresh", true);
$("#chosenmodel-button").addClass("ui-icon-carat-d ui-btn-icon-right");
if (!funfeatureOn) {
} else {
$('ul#chosenmodel-menu').find("a.ui-btn:contains(SMALL-WAVE)").addClass("optionfun");
$('ul#chosenmodel-menu').find("a.ui-btn:contains(STEP-UP)").addClass("optionstepup");
}
prodata.sort(SortById); //we need this otherwise prodata is not usable by the $('#chosenpro').trigger
$("#chosenmodel-button span").attr({ 'data-i18n': 'select.3' });
$("#boardselection").i18n();
listviewdeferred.resolve();
return listviewdeferred;
}
This dynamically displays a long list of images and text, depending on wether filters (checkboxes) are checked or not, and this list is quite long to generate and particularly difficult to scroll in iOS.... The performance is bad.
Can you help me figure out a way to improve the performance.
The accepted answer is not correct. Your code is slow, but not because of jQuery Mobile. Take a look at the loop in your jsFiddle: jsfiddle.net/L3gr46s8/4
for (i = 0; i <= 50; i++) {
$('ul[data-role="listview"]').append('<li>' + 'list item ' + i + '</li>');
$('ul[data-role="listview"]').listview('refresh');
}
There are a couple very significant problems in those four lines of code.
First, your code executes two DOM traversals to locate the ul on the page. You could execute $('ul[data-role="listview"]') before the loop and store the result in a variable:
var listView = $('ul[data-role="listview"]');
Second, your code inserts the list item directly to the DOM and you instruct jQuery Mobile to immediately apply markup enhancement with .listview('refresh');. This is extraordinarily expensive! Especially on under-powered mobile devices. Depending upon the browser and page layout, those two lines could trigger a full page re-draw with every iteration of the loop.
You should render the content as a DocumentFragment (in memory), insert it all to the DOM in one action and tell JQM to enhance the markup once at the end. Even simply moving $('ul[data-role="listview"]').listview('refresh'); out of the loop would be a dramatic improvement.
Here is some additional reading on the importance of rendering your content first in memory before inserting it into the DOM:
How expensive is it to dynamically insert DIVs using JavaScript?
John Resig - DOM DocumentFragments
To put it short, jQuery Mobile is simply slow.
I have a dynamic listview in my app and also had performance issues when using jQuery Mobile. I came to a conclusion that the issue was in rendering and was caused by jQuery Mobile. I implemented my own styling and the rendering time came down from 170ms to 25ms.
Here's some backup to my point (3 articles): http://apachecordova.blogspot.fi/search/label/jQuery%20Mobile
EDIT:
As an answer to your question in the comments, I don't think it would help if I posted my code here. The whole point is that you only write the code YOU need. My listview probably is totally different than yours.
To prove my point (again), I made two listviews. The first one is a basic jQM listview. The other one is styled with custom CSS and it's pretty close to what I use in my app. There's a button in both which renders the listview. What is happening under the hood is very different:
jQM: As you can see, there's a lot of stuff (that you may not need) going on
Custom CSS: attached an event listener to all the elements to make the comparison more fair
These profiles have been recorded with Chrome Developer Tools and the difference is obvious: 173ms vs 12ms. This custom CSS took me about 5min to write:
#custom-listview {
list-style-type: none;
padding: 0px;
margin: 0px;
}
#custom-listview li {
display: block;
position: relative;
overflow: visible;
}
#custom-listview a {
display: block;
position: relative;
text-overflow: ellipsis;
text-decoration: none;
color: white;
padding: .7em 1em;
font-size: 16px;
background-color: #333;
border: solid 1px #1f1f1f;
overflow: hidden;
white-space: nowrap;
font-family: sans-serif;
}
I had to add some code here, because SO won't let me link to Fiddle without:
jQM
Custom CSS
I'm not saying jQuery Mobile is all bad. It's good for many things. But if you have complex structure and/or a lot of data, the performance may become an issue especially in PhoneGap apps. That's the conclusion I have come to with my little experience.

How can i apply the custom style to DatePicker from the CssResources class?

I have a DatePicker widget for which i need to apply the custom styles for it. To achive it, what i have done is, in applications main css file, I have added the css styles as below
.dRPLable {
font-size: 10px;
font-weight: bold;
color: gray;
}
.dRPBtn {
background-color: #38B0DE;
background: #38B0DE;
color: white;
}
.dRPLB {
background: #EEEEEE;
}
.dRPPopup {
width: 8em;
height: 2em;
}
.mycal .datePickerPreviousButton { visibility: visible; color: gray; }
.mycal .datePickerNextButton { visibility: visible; color: gray; }
.mycal .datePickerWeekdayLabel{background: white;}
.mycal .datePickerMonthSelector { background: white; }
.mycal .datePickerMonth { background: white;color: orange;}
.mycal .datePickerWeekendLabel{background: white;}
.mycal .datePickerDayIsValue { background: orange;}
.mycal .datePickerDayIsWeekend { background: #D9D9D9;}
Then everything is applying perfectly
I am making it as a independent custom widget, for that i have created a separate css file.
To access it I have created a resources interface as below,
import com.google.gwt.core.client.GWT;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.ImageResource;
public interface DateRangeTypePickerResources extends ClientBundle {
DateRangeTypePickerResources INSTANCE = GWT
.create(DateRangeTypePickerResources.class);
/*
* #Source("DocumentImg.jpg") ImageResource getDocumentImg();
*/
#Source("down.png")
ImageResource getDownImg();
#Source("up.png")
ImageResource getUpImg();
public interface DateRangeTypePickerCssStyle extends CssResource {
String dRPLable();
String dRPBtn();
String dRPLB();
String mycal();
String dRPPopup();
String datePickerPreviousButton();
String datePickerNextButton();
String datePickerWeekdayLabel();
String datePickerWeekendLabel();
String datePickerMonthSelector();
String datePickerMonth();
String datePickerDayIsValue();
String datePickerDayIsWeekend();
}
#Source("../widget/DateRangeTypePicker.css")
DateRangeTypePickerCssStyle DateRangeTypePickerCss();
}
How i am accessing those methods as below
public class DateRangePicker extends Composite {
static DateRangeTypePickerResources resources = GWT
.create(DateRangeTypePickerResources.class);
DateRangeTypePickerCssStyle css = DateRangeTypePickerResources.INSTANCE
.DateRangeTypePickerCss();
static {
DateRangeTypePickerResources.INSTANCE.DateRangeTypePickerCss()
.ensureInjected();
}
public DateRangePicker(DateRangePickerModel dateRangePickerModel) {
Label startDateLabel = new Label("Start Date");
startDtToday.setStylePrimaryName(css.dRPLabel());
flexTable.setWidget(0, 0, startDateLabel);
DatePicker startDatePicker = new DatePicker();
startDatePicker.setStyleName(css.mycal());
}
}
Here style is applying for Label. But Style is not applying for DatePicker.
How can i apply the custom style to DatePicker from the CssResources class?
Before posting such a huge and repeated question search if the question is already asked or not... There are many similar question to it.
And the answer for your question is in the following link
ClickHere

Random changing backgrounds WITH fade effect

Aloha Stockoverflow.
In advance, thank you!
I am trying to modify my randomly changing background on my webpage, to add a FADE effect, so the change from 1 background to another is not so sudden and sharp.
I have tried to search through the web endlessly for a solution to my issue, but it all points towards adding a jQuery plugin which I would preferably avoid if it is possible.
My working code is as follows and needs to have added some kind of fadein / fadeout effect.
<script type="text/javascript">
var num;
var temp=0;
var speed=5000; /* this is set for 5 seconds, edit value to suit requirements */
var preloads=[];
/* add any number of images here */
preload(
'images/bg1.jpg',
'images/bg2.jpg',
'images/bg3.jpg',
'images/bg4.jpg',
'images/bg5.jpg'
);
function preload(){
for(var c=0;c<arguments.length;c++) {
preloads[preloads.length]=new Image();
preloads[preloads.length-1].src=arguments[c];
}
}
function rotateImages() {
num=Math.floor(Math.random()*preloads.length);
if(num==temp){
rotateImages();
}
else {
document.body.style.backgroundImage='url('+preloads[num].src+')';
temp=num;
setTimeout(function(){rotateImages()},speed);
}
}
if(window.addEventListener){
window.addEventListener('load',rotateImages,false);
}
else {
if(window.attachEvent){
window.attachEvent('onload',rotateImages);
}
}
</script>
Thank you very much for taking the time to look at it. :)
How to do it without plugins:
Use 2 layers for the background image, position them on top of each other.
Init the page with the first image on the bottom layer, make the top layer invisible (using CSS opacity property, make sure to Google this, different browsers use different approaches).
When fading:
Set the new image for the top layer.
Use a short, looping (frameduration < 40ms) setTimeout to increment the opacity of your top layer to 1. Use increments of 1/(speed/frameduration).
When comletely faded in, set the bottom layer to use the new (now visible) image, and set the top layer to opacity 0.
Like this:
<html>
<head>
<script type="text/javascript">
var num;
var current=0;
var speed=5000; /* this is set for 5 seconds, edit value to suit requirements */
var fps = 25;
var fadeDuration = 1000;
var opacityIncrement = 1/(fadeDuration/(1000/fps));
var preloads=[];
var topLayerOpacity = 0;
var topLayer = document.createElement("div");
var bottomLayer = document.createElement("div");
setOpacity(topLayer, 0);
/* add any number of images here */
preload(
'images/bg1.jpg',
'images/bg2.jpg',
'images/bg3.jpg',
'images/bg4.jpg'
);
function loadComplete(){
//add layers to background div
document.getElementById('backgroundContainer').appendChild(bottomLayer);
document.getElementById('backgroundContainer').appendChild(topLayer);
rotateImages();
}
function preload(){
//preload images
for(var c=0;c<arguments.length;c++) {
preloads[preloads.length]=new Image();
preloads[preloads.length-1].src=arguments[c];
}
}
// selecte new random image from preloads and start fade-in
function rotateImages() {
num=Math.floor(Math.random()*preloads.length);
//don't select current image
if(num==current){
rotateImages();
}
else {
topLayer.style.backgroundImage = 'url('+preloads[num].src+')';
current=num;
//start fade-in
fadeIn();
setTimeout(function(){rotateImages()},speed);
}
}
// fade in topLayer
function fadeIn(){
if (topLayerOpacity < 1){
topLayerOpacity += opacityIncrement;
setOpacity(topLayer, topLayerOpacity);// opacityIncrement);
setTimeout(fadeIn, 1000/fps);
}else{
fadeInComplete();
}
}
//return opacity for element
function getOpacity(el){
alert (el.style.opacity);
return el.style.opacity;
}
//sets opacity on element
function setOpacity(el, val){
el.style.opacity = val;
el.style.filter = 'alpha(opacity=' + val*100 + ')';
}
//called when fadeIn completed
function fadeInComplete(){
bottomLayer.style.backgroundImage = topLayer.style.backgroundImage;
topLayerOpacity = 0;
setOpacity(topLayer, topLayerOpacity);
}
if(window.addEventListener){
window.addEventListener('load',loadComplete,false);
}
else {
if(window.attachEvent){
window.attachEvent('onload',loadComplete);
}
}
</script>
<style type="text/css">
#backgroundContainer{
width:100%;
height:100%;
position:absolute;
/*background-color:green;*/
}
#backgroundContainer div{
width:100%;
height:100%;
position:absolute;
top:0;
}
.page {
width:100%;
text-align:center;
position:absolute;
}
.contents{
width:400px;
margin:0 auto;
background-color:lightblue;
}
</style>
</head>
<body>
<!-- holds background layers -->
<div id="backgroundContainer"></div>
<!-- substitutes for 'body' on this webpage -->
<div class="page">
<!-- contents for your webpage, through css centered within page-div -->
<div class="contents">
<p>Contents</p>
</div>
</div>
</body>
</html>
OR
Use jQuery/mootools/script.aculo.us/...
Best of luck!

Resources