Related
I´m using appcelerator studio %.2.0 GA webView with chartjs plugin line update.
With this code below, I have a perfect view in web browser like Chrome (without webview), but on android smartphone or emulator it not runs correctly. Appears a red line on the bottom and double line on the strokeGrid.
Does anybody help ?
This is the code:
Javascript-
var count = 0;
//
// create base UI tab and root window
//
var win = Titanium.UI.createWindow({
title:'BtTest',
backgroundColor:'#eff2d8',
layout: 'vertical'
});
var mainView = Ti.UI.createView({
top: 0,
width: Ti.UI.SIZE,
height: Ti.UI.SIZE,
backgroundColor: '#7cd0F7',
});
var webView = Ti.UI.createWebView({
backgroundColor: '#F0F8FF',
top:100,
left: 0,
height: Ti.UI.FILL,
width: Ti.UI.FILL,
cacheMode: Ti.UI.Android.WEBVIEW_LOAD_NO_CACHE,
borderColor: 'black',
url: 'html/lineChart.html'
});
mainView.add(webView);
function send(value)
{
Ti.App.fireEvent("app:fromChart", {message: value});
Ti.API.info('Sent: ', value);
count ++;
Ti.API.info("Count: " + count);
}
function interval()
{
setInterval(function()
{
send(Math.floor(Math.random() * 100));
}, 500);
}
interval();
win.add(mainView);
win.open();
HTML-
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
<script src="../js/Chart.min.js"></script>
</head>
<body>
<canvas id="updating-chart" width="320" height="220"> </canvas>
<script>
var N = 20;
var zero_array = [];
for (i = 0; i < N; i++)
zero_array.push("");
var canvas = document.getElementById('updating-chart'),
ctx = canvas.getContext('2d'),
startingData = {
labels: zero_array,
datasets: [
{
strokeColor: "rgba(255,0,0,1)",
data: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
}]
},
latestLabel = startingData.labels[0];
var lineOptions = {
bezierCurve: false,
scaleOverlay : false,
scaleOverride : false,
scaleSteps : null,
scaleStepWidth : null,
scaleStartValue : null,
scaleLineColor : "rgba(0,0,0,1)",
scaleLineWidth : 1,
scaleShowLabels : true,
scaleLabel : "<%=value%>",
scaleFontFamily : "'Arial'",
scaleFontSize : 12,
scaleFontStyle : "normal",
scaleFontColor : "#666",
scaleShowGridLines : true,
scaleGridLineColor : "rgba(0,0,0,1)",
scaleGridLineWidth : 1,
pointDot : true,
pointDotRadius : 0,
pointDotStrokeWidth : 1,
datasetStroke : true,
datasetStrokeWidth : 2,
datasetFill : false,
animation : false,
responsive: false,
maintainAspectRatio: true
};
// We wait for everything to be loaded
window.onload = function main()
{
// Get the context of the canvas
var ctx = document.getElementById("line_example").getContext("2d");
// Create the Chart object
var line_example_chart = new Chart(ctx).Line(data,lineOptions);
// Used for the labels on the X axis
var label_idx = 1;
Ti.App.addEventListener("app:fromChart", function(e)
{
var msg = e.message;
if(msg == 0) msg = 1;
line_example_chart.removeData();
line_example_chart.addData([msg], label_idx++);
});
window();
};
</script>
If you are building for an older version of android, the webview for appcelerator was not Chromium based which might cause things to look a bit different. You can read more about it here.
A quote from the WebView documentation for appcelerator:
Starting with Android 4.4 (API Level 19), the WebView component is based off of Chromium, introducing a number of changes to its rendering engine. Web content may look or behave differently depending on the Android version. The WebView does not have full feature parity with Chrome for Android.
I have a jsfiddle here using jquery 1.8.3 and scrollTo to scoll to an element #en on an XY axis
https://jsfiddle.net/80kxdsxe/
var $scrollTo = $.scrollTo = function(target, duration, settings) {
return $(window).scrollTo(target, duration, settings);
};
$scrollTo.defaults = {
axis:'xy',
duration: 0,
limit:true
};
The jsfiddle that i need to work is using pure js but it only scrolls to the element #en on either the X or Y axis and i need it to scroll using XY
https://jsfiddle.net/43s8wpd7/
/* Note: In order to be subjected to chaining and animation options, scroll's tweening is routed through Velocity as if it were a standard CSS property animation. */
if (action === "scroll") {
/* The scroll action uniquely takes an optional "offset" option -- specified in pixels -- that offsets the targeted scroll position. */
var scrollDirection = (/^x$/i.test(opts.axis) ? "Left" : "Top"),
scrollOffset = parseFloat(opts.offset) || 0,
scrollPositionCurrent,
scrollPositionCurrentAlternate,
scrollPositionEnd;
if (opts.container) {
if (Type.isWrapped(opts.container) || Type.isNode(opts.container)) {
opts.container = opts.container[0] || opts.container;
scrollPositionCurrent = opts.container["scroll" + scrollDirection];
scrollPositionEnd = (scrollPositionCurrent + $(element).position()[scrollDirection.toLowerCase()]) + scrollOffset; /* GET */
} else {
opts.container = null;
}
} else {
scrollPositionCurrent = Velocity.State.scrollAnchor[Velocity.State["scrollProperty" + scrollDirection]];
scrollPositionCurrentAlternate = Velocity.State.scrollAnchor[Velocity.State["scrollProperty" + (scrollDirection === "Left" ? "Top" : "Left")]]; /* GET */
scrollPositionEnd = $(element).offset()[scrollDirection.toLowerCase()] + scrollOffset; /* GET */
}
/* Since there's only one format that scroll's associated tweensContainer can take, we create it manually. */
tweensContainer = {
scroll: {
rootPropertyValue: false,
startValue: scrollPositionCurrent,
currentValue: scrollPositionCurrent,
endValue: scrollPositionEnd,
unitType: "",
easing: opts.easing,
scrollData: {
container: opts.container,
direction: scrollDirection,
alternateValue: scrollPositionCurrentAlternate
}
},
element: element
};
if (Velocity.debug) console.log("tweensContainer (scroll): ", tweensContainer.scroll, element);
Can anyone help me fix the second NON-jquery fiddle to allow the scroll to be on the XY axis?
Thanks.
I've read many answers to the issue of map not being displayed when put inside nested divs, i tried everything but still could not get it working.
I need to have a splitter between directions panel and the actual map, and i am using the code i've found for splitter, combined with the map code. But map is never displayed, only grey background.
I have to admit i am illiterate with HTML design, CSS and JavaScript. Please if anyone could easily see the issue, i would be very grateful. Thank you.
<HTML>
<HEAD>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false&language=en&libraries=drawing&key=AIzaSyASrsCWlCQ1YlOlkgyf3tMQf8EiOU8rKv0"></script>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.8.2.min.js"></script>
<style>
html, body, {
margin: 0;
padding: 0;
height: 500px;
width: 500px;
}
.simple {
height: 1000px;
width: 1000px;
position: absolute
}
.simple div {
overflow: auto
}
.hsplitbar {
height: 5px;
background: #cab
}
.hsplitbar:hover{
background: #eab
}
</style>
<script type="text/javascript">
;(function($){
$.fn.splitter = function(args){
args = args || {};
return this.each(function() {
var zombie; // left-behind splitbar for outline resizes
function startSplitMouse(evt) {
if ( opts.outline )
zombie = zombie || bar.clone(false).insertAfter(A);
panes.css("-webkit-user-select", "none"); // Safari selects A/B text on a move
bar.addClass(opts.activeClass);
A._posSplit = A[0][opts.pxSplit] - evt[opts.eventPos];
$(document)
.bind("mousemove", doSplitMouse)
.bind("mouseup", endSplitMouse);
}
function doSplitMouse(evt) {
var newPos = A._posSplit+evt[opts.eventPos];
if ( opts.outline ) {
newPos = Math.max(0, Math.min(newPos, splitter._DA - bar._DA));
bar.css(opts.origin, newPos);
} else
resplit(newPos);
}
function endSplitMouse(evt) {
bar.removeClass(opts.activeClass);
var newPos = A._posSplit+evt[opts.eventPos];
if ( opts.outline ) {
zombie.remove(); zombie = null;
resplit(newPos);
}
panes.css("-webkit-user-select", "text"); // let Safari select text again
$(document)
.unbind("mousemove", doSplitMouse)
.unbind("mouseup", endSplitMouse);
}
function resplit(newPos) {
// Constrain new splitbar position to fit pane size limits
newPos = Math.max(A._min, splitter._DA - B._max,
Math.min(newPos, A._max, splitter._DA - bar._DA - B._min));
// Resize/position the two panes
bar._DA = bar[0][opts.pxSplit]; // bar size may change during dock
bar.css(opts.origin, newPos).css(opts.fixed, splitter._DF);
A.css(opts.origin, 0).css(opts.split, newPos).css(opts.fixed, splitter._DF);
B.css(opts.origin, newPos+bar._DA)
.css(opts.split, splitter._DA-bar._DA-newPos).css(opts.fixed, splitter._DF);
// IE fires resize for us; all others pay cash
if ( !$.browser.msie )
panes.trigger("resize");
}
function dimSum(jq, dims) {
// Opera returns -1 for missing min/max width, turn into 0
var sum = 0;
for ( var i=1; i < arguments.length; i++ )
sum += Math.max(parseInt(jq.css(arguments[i])) || 0, 0);
return sum;
}
// Determine settings based on incoming opts, element classes, and defaults
var vh = (args.splitHorizontal? 'h' : args.splitVertical? 'v' : args.type) || 'v';
var opts = $.extend({
activeClass: 'active', // class name for active splitter
pxPerKey: 8, // splitter px moved per keypress
tabIndex: 0, // tab order indicator
accessKey: '' // accessKey for splitbar
},{
v: { // Vertical splitters:
keyLeft: 39, keyRight: 37, cursor: "e-resize",
splitbarClass: "vsplitbar", outlineClass: "voutline",
type: 'v', eventPos: "pageX", origin: "left",
split: "width", pxSplit: "offsetWidth", side1: "Left", side2: "Right",
fixed: "height", pxFixed: "offsetHeight", side3: "Top", side4: "Bottom"
},
h: { // Horizontal splitters:
keyTop: 40, keyBottom: 38, cursor: "n-resize",
splitbarClass: "hsplitbar", outlineClass: "houtline",
type: 'h', eventPos: "pageY", origin: "top",
split: "height", pxSplit: "offsetHeight", side1: "Top", side2: "Bottom",
fixed: "width", pxFixed: "offsetWidth", side3: "Left", side4: "Right"
}
}[vh], args);
// Create jQuery object closures for splitter and both panes
var splitter = $(this).css({position: "relative"});
var panes = $(">*", splitter[0]).css({
position: "absolute", // positioned inside splitter container
"z-index": "1", // splitbar is positioned above
"-moz-outline-style": "none" // don't show dotted outline
});
var A = $(panes[0]); // left or top
var B = $(panes[1]); // right or bottom
// Focuser element, provides keyboard support; title is shown by Opera accessKeys
var focuser = $('')
.attr({accessKey: opts.accessKey, tabIndex: opts.tabIndex, title: opts.splitbarClass})
.bind($.browser.opera?"click":"focus", function(){ this.focus(); bar.addClass(opts.activeClass) })
.bind("keydown", function(e){
var key = e.which || e.keyCode;
var dir = key==opts["key"+opts.side1]? 1 : key==opts["key"+opts.side2]? -1 : 0;
if ( dir )
resplit(A[0][opts.pxSplit]+dir*opts.pxPerKey, false);
})
.bind("blur", function(){ bar.removeClass(opts.activeClass) });
// Splitbar element, can be already in the doc or we create one
var bar = $(panes[2] || '<div></div>')
.insertAfter(A).css("z-index", "100").append(focuser)
.attr({"class": opts.splitbarClass, unselectable: "on"})
.css({position: "absolute", "user-select": "none", "-webkit-user-select": "none",
"-khtml-user-select": "none", "-moz-user-select": "none"})
.bind("mousedown", startSplitMouse);
// Use our cursor unless the style specifies a non-default cursor
if ( /^(auto|default|)$/.test(bar.css("cursor")) )
bar.css("cursor", opts.cursor);
// Cache several dimensions for speed, rather than re-querying constantly
bar._DA = bar[0][opts.pxSplit];
splitter._PBF = $.boxModel? dimSum(splitter, "border"+opts.side3+"Width", "border"+opts.side4+"Width") : 0;
splitter._PBA = $.boxModel? dimSum(splitter, "border"+opts.side1+"Width", "border"+opts.side2+"Width") : 0;
A._pane = opts.side1;
B._pane = opts.side2;
$.each([A,B], function(){
this._min = opts["min"+this._pane] || dimSum(this, "min-"+opts.split);
this._max = opts["max"+this._pane] || dimSum(this, "max-"+opts.split) || 9999;
this._init = opts["size"+this._pane]===true ?
parseInt($.curCSS(this[0],opts.split)) : opts["size"+this._pane];
});
// Determine initial position, get from cookie if specified
var initPos = A._init;
if ( !isNaN(B._init) ) // recalc initial B size as an offset from the top or left side
initPos = splitter[0][opts.pxSplit] - splitter._PBA - B._init - bar._DA;
if ( opts.cookie ) {
if ( !$.cookie )
alert('jQuery.splitter(): jQuery cookie plugin required');
var ckpos = parseInt($.cookie(opts.cookie));
if ( !isNaN(ckpos) )
initPos = ckpos;
$(window).bind("unload", function(){
var state = String(bar.css(opts.origin)); // current location of splitbar
$.cookie(opts.cookie, state, {expires: opts.cookieExpires || 365,
path: opts.cookiePath || document.location.pathname});
});
}
if ( isNaN(initPos) ) // King Solomon's algorithm
initPos = Math.round((splitter[0][opts.pxSplit] - splitter._PBA - bar._DA)/2);
// Resize event propagation and splitter sizing
if ( opts.anchorToWindow ) {
// Account for margin or border on the splitter container and enforce min height
splitter._hadjust = dimSum(splitter, "borderTopWidth", "borderBottomWidth", "marginBottom");
splitter._hmin = Math.max(dimSum(splitter, "minHeight"), 20);
$(window).bind("resize", function(){
var top = splitter.offset().top;
var wh = $(window).height();
splitter.css("height", Math.max(wh-top-splitter._hadjust, splitter._hmin)+"px");
if ( !$.browser.msie ) splitter.trigger("resize");
}).trigger("resize");
}
else if ( opts.resizeToWidth && !$.browser.msie )
$(window).bind("resize", function(){
splitter.trigger("resize");
});
// Resize event handler; triggered immediately to set initial position
splitter.bind("resize", function(e, size){
// Custom events bubble in jQuery 1.3; don't Yo Dawg
if ( e.target != this ) return;
// Determine new width/height of splitter container
splitter._DF = splitter[0][opts.pxFixed] - splitter._PBF;
splitter._DA = splitter[0][opts.pxSplit] - splitter._PBA;
// Bail if splitter isn't visible or content isn't there yet
if ( splitter._DF <= 0 || splitter._DA <= 0 ) return;
// Re-divvy the adjustable dimension; maintain size of the preferred pane
resplit(!isNaN(size)? size : (!(opts.sizeRight||opts.sizeBottom)? A[0][opts.pxSplit] :
splitter._DA-B[0][opts.pxSplit]-bar._DA));
}).trigger("resize" , [initPos]);
});
};
})(jQuery);
var geocoder;
var map;
var startMarker;
var AllMarkers = [];
var routeRectangle;
var drawingManager;
var directionsService;
var directionsDisplay;
var waypts = [];
var routeWaypts = [];
var routeMarkers = [];
var start;
var finish;
var route;
var addresses = [];
var innerHTML;
var totalDistance = 0;
var totalDuration = 0;
function initialize() {
$().ready(function() {
$(".simple").splitter({type: 'h', accessKey: 'M'});
});
geocoder = new google.maps.Geocoder();
var myOptions = {
mapTypeId: google.maps.MapTypeId.ROADMAP,
panControl: true,
zoomControl: true,
mapTypeControl: true,
scaleControl: true,
streetViewControl: true,
overviewMapControl: true
};
map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
geocoder.geocode({'address': 'US'}, function (results, status) {
var ne = results[0].geometry.viewport.getNorthEast();
var sw = results[0].geometry.viewport.getSouthWest();
map.setZoom(20);
map.fitBounds(results[0].geometry.viewport);
});
</script>
</head>
<body onload="initialize()">
<div class="simple">
<div id="directions_panel" style="500px;height:500px"></div>
<div id="map_canvas" style="500px;height:500px"></div>
</div>
</body>
</html>
replace this:
.simple div {
overflow: auto
}
with that:
.simple>div {
overflow: auto
}
I'm stuck trying to get a very simple task to work on my app. Essentially, I want to be able to add view to a ScrollView when users click a button. Let's call this first view added a "Card". Inside this card, I also have another scrollview which is supposed to then dynamically receive and display "categories" views.
For the most part everything is working fine: when the "Add" button at the top is clicked, the "card" is created and I am able to see all of the information, HOWEVER, the problem is that each time I "Add" a card, I end up not only adding "categories" views to the current created card by a factor of 2, but also the same happens to the previously added cards. Like the following:
I hope this makes sense.
Here's my code (my button event listener):
addViewButton.addEventListener('click', function(e) {
var url = "https://my.apilink.com";
var xhr = Titanium.Network.createHTTPClient();
xhr.open('GET', url);
xhr.onload = function() {
var response = JSON.parse(this.responseText);
var t = response.data.categories;
var bons = [];
for (var item in t) {
bons.push(t[item]);
}
Ti.API.info("Data received" + bons);
var resp = {
response : bons
};
createCard(resp);
};
xhr.send();
}
And here's where it all happens (creating the views (cards)):
var catScrollView = Titanium.UI.createScrollView({
top : 130,
left : 0,
right : 0,
contentWidth : '100%',
showHorizontalScrollIndicator : false,
showVerticalScrollIndicator : true,
});
var scrollView = Ti.UI.createScrollView({
top : 130,
left : 0,
right : 0,
backgroundColor : 'white',
contentWidth : '100%',
showHorizontalScrollIndicator : false,
showVerticalScrollIndicator : true,
});
var topPosition = 20;
var leftPosition = 20;
var topPositionCat = 30;
var i = 0;
function createCard(_args) {
var response = _args.response;
Ti.API.info("Response" + response);
var colorArr = ["red", "orange", "blue", "green", "pink", "yellow"];
var fakeArray = ["card 0", "card 1", "card 2", "card 3"];
var ranIndex = getRandom(colorArr.length);
i++;
for (var d = 0; d < response.length; d++) {
var panelImage = Ti.UI.createView({
backgroundColor : colorArr[ranIndex],
top : topPosition + (i * 60),
borderRadius : 5,
borderColor : 'white',
borderWidth : 2,
id : i,
bit : false,
active : false,
height : 350,
width : 290,
});
//add a few attributes to the card
var cardTitle = Ti.UI.createLabel({
text : "I am card # " + i,
top : 10,
left : 0,
color : 'black'
});
panelImage.add(cardTitle);
//Add the EventListener for the view.
panelImage.addEventListener('singletap', cardButtonHandler);
//Add scrollview and a card here
var leftPosition = 20;
if (d % 2 == 0) {
leftPosition = 20;
} else {
leftPosition = 180;
}
var panelImageCat = Ti.UI.createView({
backgroundImage : '/images/row_bg.png',
top : topPositionCat,
left : leftPosition,
height : 100,
width : 100,
});
var catImageButton = Ti.UI.createImageView({
image : response[d].icon,
name : response[d].name,
id : response[d].id,
icon : response[d].icon,
width : 90,
height : 90,
top : 4
});
panelImageCat.add(catImageButton);
var catName = Ti.UI.createLabel({
text : response[d].name,
textAlign : 'center',
color : 'black',
top : 3,
font : {
fontWeight : 'bold'
}
});
panelImageCat.add(catName);
if (leftPosition == 180) {
topPositionCat += panelImageCat.height + 10;
}
// add the view in scroll view
catScrollView.add(panelImageCat);
panelImage.add(catScrollView);
// Add the EventListener for the view.
catImageButton.addEventListener('click', function(e) {
alert(e.source.name);
});
}// END FOR LOOP
catScrollView.contentHeight = topPositionCat + 20;
// add the view in scroll view
scrollView.add(panelImage);
}
Of course there's more code for when users tap on each "card" etc. But that's irrelevant.
This is probably very simple to solve. I suspect it has to do with how I am setting up the loops to create each card and its corresponded categories views etc.
Any help, guidance is highly appreciated.
Thank you!
I guess that the main problem of your code is the way you are adding cards to the row. You shouldn't add a card for each category, but only create a card, add each category to that card, and then, add the card to the parent scrollView.
Nevertheless, in order to improve the readability of your code (and thus, your capability to maintain it), you should consider doing two things :
Move all the code that concern styling inside a separated file.
Create "builders" to instantiate views components so that, your main code only contains relevant information.
Here is an example on TiFiddle, and right below, explanation about it.
Move styling in a separated file
Just make a really easy-to-use commonJS-like module. Titanium supports them, do not be shy.
styles.js
/* ------ Top Level Components ------ */
exports.mainWindow = {
backgroundColor: "#ffffff",
layout: "vertical"
};
exports.topLevelScrollView = {
width: Ti.UI.FILL,
height: Ti.UI.FILL,
contentWidth : '100%',
showHorizontalScrollIndicator : false,
showVerticalScrollIndicator : true
};
exports.addButton = {
width: Ti.UI.FILL,
backgroundColor: "#999999",
color: "#ffffff",
font: { fontSize: 14 },
height: 50
};
/* ------ Card ----*/
exports.cardContainer = {
borderRadius : 5,
borderColor : '#ffffff',
borderWidth : 2,
top: 0,
height : 350,
width : Ti.UI.FILL,
};
exports.cardTitle = {
top : 10,
left : 10,
height: Ti.UI.SIZE,
width: Ti.UI.FILL,
color : '#141414'
};
exports.cardScrollView = {
layout: "horizontal",
contentWidth : '100%',
showHorizontalScrollIndicator : false,
showVerticalScrollIndicator : true,
top: 50,
height: Ti.UI.SIZE,
width: Ti.UI.SIZE
};
/* ------ Category ------*/
exports.categoryContainer = {
backgroundColor: "#cccccc",
opacity: "0.8",
left: 30,
right: 30,
top: 10,
bottom: 10,
height : 100,
width : 100,
};
exports.categoryImage = {
width : 90,
height : 90,
top : 4
};
exports.categoryTitle = {
textAlign : 'center',
color : 'black',
top : 3,
font : {
fontWeight : 'bold'
}
};
/* TIPS: Always use fancy colors for your tests <3 */
exports.colors = [
"#c0392b",
"#e67e22",
"#3498db",
"#2ecc71",
"#9b59b6",
"#f1c40f"
];
Create also some utils
The way you are creating card and categories has nothing to go alongside the other function. So, let's create another module.
ui_utils.js
exports.pickColor = function (colors) {
var randomIndex = Math.floor(Math.random() * colors.length);
return colors[randomIndex];
};
/* Create and return a cardContainer that may hold several categoryContainers */
exports.createCardView = function (id, title, color, styles, listener) {
var cardContainer = Ti.UI.createView(styles.cardContainer),
cardTitle = Ti.UI.createLabel(styles.cardTitle),
cardScrollView = Ti.UI.createScrollView(styles.cardScrollView);
cardContainer.id = id;
cardContainer.bit = false;
cardContainer.active = false;
cardContainer.backgroundColor = color;
cardContainer.cardScrollView = cardScrollView;
cardTitle.text = title;
cardContainer.addEventListener(listener.eventName, listener.listener);
cardContainer.add(cardTitle);
cardContainer.add(cardScrollView);
return cardContainer;
};
/* Create and return a categoryContainer */
exports.createCategoryView = function (category, styles, listener) {
var categoryContainer = Ti.UI.createView(styles.categoryContainer),
categoryTitle = Ti.UI.createLabel(styles.categoryTitle),
categoryImage = Ti.UI.createImageView(styles.categoryImage);
categoryTitle.text = category.name;
categoryImage.id = category.id;
categoryImage.name = category.name;
categoryImage.image = category.icon;
categoryImage.addEventListener(listener.eventName, listener.listener);
categoryContainer.add(categoryTitle);
categoryContainer.add(categoryImage);
return categoryContainer;
};
Rewrite your main file
And finally, the main function with just the minimal amount of code.
app.js
/* Start by requiring defined modules, and creating different views */
var styles = require('styles'),
uiUtils = require('ui_utils'),
mainWindow = Ti.UI.createWindow(styles.mainWindow),
topLevelScrollView = Ti.UI.createScrollView(styles.topLevelScrollView),
addButton = Ti.UI.createButton(styles.addButton);
/* The famous one */
function createCard(_args) {
var response = _args.response;
Ti.API.info("Response :" + response);
/* Create the new card */
var id = topLevelScrollView.children.length, /* Ugly .. find another way */
title = "I am card #" + id,
color = uiUtils.pickColor(styles.colors),
listener = {
eventName: "singleTap",
listener: function () { } /* Just supply a listener */
},
cardContainer = uiUtils.createCardView(id, title, color, styles, listener);
cardContainer.top = 50 * id;
/* Iterate over each category that we have in response */
for (var i = 0, category; category = response[i]; i++) {
/* Create the category view */
var listener = {
eventName: "click",
listener: function(e) { alert(e.source.name); }
},
categoryContainer = uiUtils.createCategoryView(category, styles, listener);
/* Add it to the card scrollView */
cardContainer.cardScrollView.add(categoryContainer);
}
/* Dont forget to add the card to the topLevelScrollView */
topLevelScrollView.add(cardContainer);
}
/* Initialize the different views */
addButton.title = "ADD";
mainWindow.add(addButton);
mainWindow.add(topLevelScrollView);
mainWindow.open();
Enjoy!
Let's say that I want to make a webpage that will show approximate number of free parking spots left on each parking(due to confidentiality I'm not allowed to give any details of what I'm actually doing). For this I'm using Google maps and Markerclusterer. So for parking with less than 5% spots left I'd like to show a red marker, for parkings with 5%-25% spots I'd show a yellow one and for ones with more than 25% free spots I want to make it green. So far I could make those markers and cluster them, but here's the tricky part(and question itself):
How can I make a cluster icon dependant on the markers inside it?
For example:
Parking A is green
Parking B is red
Parking C is green
Parking D is yellow
When zoomed out I want to show the cluster that has all 4 of them red(worst of all). When zoomed in I'd get 2 clusters(A+B and C+D). I want the first cluster(A+B) to be red and second(C+D) should be yellow.
What I did so far:
var mcOptions = {gridSize: 50, maxZoom: 15, styles: [{
height: 46,
url: "///Users/Novarg/Downloads/foundation-5.4.7/img/greenC.png",
width: 46
},
{
height: 46,
url: "///Users/Novarg/Downloads/foundation-5.4.7/img/redC.png",
width: 46
}]};
var markers = [];
for (var i = 0; i < 100; i++) {
var latLng = new google.maps.LatLng(51 + Math.random(),
4 + Math.random());
var marker = new google.maps.Marker({'position': latLng, icon: 'img/greenP.png'});
markers.push(marker);
}
for (var i = 0; i < 20; i++) {
var latLng = new google.maps.LatLng(51 - Math.random(),
4 - Math.random());
var marker = new google.maps.Marker({'position': latLng, icon: 'img/redP.png'});
markers.push(marker);
}
var markerCluster = new MarkerClusterer(map, markers, mcOptions);
}
google.maps.event.addDomListener(window, 'load', initialize);
Right now I only have red and green markers, which should be enough to test it. But behaviour of this cluster now is as follows:
All clusters with less than 10 markers in it are green
All clusters with more than 9 markers in it are red
EDIT
From this link I found that what I might need is Calculator. So I tried it, but still no luck(although I think that I'm getting closer. Actually, I hope I'm very close to the solution right now).
So I tried to change my options:
var mcOptions = {gridSize: 50, maxZoom: 15, styles: [{
height: 46,
url: "///Users/Novarg/Downloads/foundation-5.4.7/img/greenC.png",
width: 46
},
{
height: 46,
url: "///Users/Novarg/Downloads/foundation-5.4.7/img/redC.png",
width: 46
}],
calculator: function(markers, numStyles) {
for (var i = 0; i < markers.length; i++) {
if (markers[i].getIcon().indexOf("redP.png") > -1) {
return {text: markers.length, index: 1};
}
}
return {text: markers.length, index: 0};
}
};
But the Calculator is never being used. I tested it by putting a simple alert('test'); inside it.
I hope this additional info will help you to help me find a solution.
As I already mentioned in the edit, I was very close to the solution. So I took another(fresh) look at the code today, checked the docs once again and noticed the following in the ClusterIconInfo:
index number The index plus 1 of the element in the styles array to be used to style the cluster icon.
So basically I solved this problem simply by incrementing the index by one(and I also moved Calculator to be a var and then used setCalculator() method on the MarkerClusterer itself). So my code became:
var calc = function(markers, numStyles) {
for (var i = 0; i < markers.length; i++) {
if (markers[i].getIcon().indexOf("redP") > -1) {
return {text: markers.length, index: 2};
}
}
return {text: markers.length, index: 1};
}
var mcOptions = {gridSize: 50, maxZoom: 15, styles: [{
height: 46,
url: "img/greenC.png",
width: 46
},
{
height: 46,
url: "img/redC.png",
width: 46
}]
};
var markerCluster = new MarkerClusterer(map, markers, mcOptions);
markerCluster.setCalculator(calc);
And now it works like a charm(as it should).
Hopefully this could help somebody someday.
Extending on Novarg's answer, you will need to define a Calculator, which selects a style from the initial styles array passed into the MarkerClusterer's constructor.
If you want to extend the original functionality (which controls the style based on the number of markers the Cluster contains, you can add styles in multiples of 3 in the initial styles array and call the original Calculator in your calculator.
// create MarkerClusterer instance with array of styles, multiples of 3
const markerClusterer = new MarkerClusterer(this.map, this.kioskMarkers, {
clusterClass: "custom-clustericon",
styles: [
{
width: 30,
height: 30,
className: "normal"
},
{
width: 40,
height: 40,
className: "normal"
},
{
width: 50,
height: 50,
className: "normal"
},
{
width: 30,
height: 30,
className: "special"
},
{
width: 40,
height: 40,
className: "special"
},
{
width: 50,
height: 50,
className: "special"
}]
});
// your function that determines special styling
const checkForYourSpecialCondition = () => true;
// new calculator function
const newCalculator = (markers, numStyles) => {
const offset = checkForYourSpecialCondition() ? 3 : 0;
const { text, index } = MarkerCluster.CALCULATOR(markers, numStyles);
return {text, index: index + offset};
}
markerClusterer.setCalculator(newCalculator);