Wordpress only admin can crop images. Get error There has been an error cropping your image - ajax

I added wp.media via js to crop images. I've got it working for admin but not for any other role specifically 'subscriber'. It sounds crazy but there are no errors at all in the console or in debug and the only error I get is There has been an error cropping your image.
I don't think its a GC issue or Imagick etc as it does work for admin.
I've tried adding permissions to the 'subscriber' role: upload_files and edit_files (which seems to be deprecated anyways). Please help and thank you in advance! Here's the code. I unfortunately do not know who to credit for this, but majority of it is not my original code:
var mediaUploader;
function myTheme_calculateImageSelectOptions(attachment, controller) {
var control = controller.get( 'control' );
var flexWidth = !! parseInt( control.params.flex_width, 10 );
var flexHeight = !! parseInt( control.params.flex_height, 10 );
var realWidth = attachment.get( 'width' );
var realHeight = attachment.get( 'height' );
var xInit = parseInt(control.params.width, 10);
var yInit = parseInt(control.params.height, 10);
var ratio = xInit / yInit;
controller.set( 'canSkipCrop', ! control.mustBeCropped( flexWidth, flexHeight, xInit, yInit, realWidth, realHeight ) );
var xImg = xInit;
var yImg = yInit;
if ( realWidth / realHeight > ratio ) {
yInit = realHeight;
xInit = yInit * ratio;
} else {
xInit = realWidth;
yInit = xInit / ratio;
}
var x1 = ( realWidth - xInit ) / 2;
var y1 = ( realHeight - yInit ) / 2;
var imgSelectOptions = {
handles: true,
keys: true,
instance: true,
persistent: true,
imageWidth: realWidth,
imageHeight: realHeight,
minWidth: xImg > xInit ? xInit : xImg,
minHeight: yImg > yInit ? yInit : yImg,
x1: x1,
y1: y1,
x2: xInit + x1,
y2: yInit + y1
};
return imgSelectOptions;
}
function myTheme_setImageFromURL(url, attachmentId, width, height) {
var choice, data = {};
data.url = url;
data.thumbnail_url = url;
data.timestamp = _.now();
if (attachmentId) {
data.attachment_id = attachmentId;
}
if (width) {
data.width = width;
}
if (height) {
data.height = height;
}
// $("#heading_picture").val( url );
// $("#heading_picture_preview").prop("src", url);
$('[name="entry-img"]').val(attachmentId)
$('.activity-add-image').css({ 'background-image': `url(${url})` }).addClass('has-image')
$('.activity-remove-image').css('display', 'block');
fieldValidation.image.validated = true
validateNewLetter()
}
function myTheme_setImageFromAttachment(attachment) {
// $("#heading_picture").val( attachment.url );
// $("#heading_picture_preview").prop("src", attachment.url);
$('[name="entry-img"]').val(attachment.id)
$('.activity-add-image').css({ 'background-image': `url(${attachement.url})` }).addClass('has-image')
$('.activity-remove-image').css('display', 'block');
fieldValidation.image.validated = true
validateNewLetter()
}
$('.activity-remove-image').on('click', function(e){
e.preventDefault();
$('.activity-add-image').css({ 'background-image': 'none' }).removeClass('has-image')
$('.activity-remove-image').hide();
$('[name="entry-img"]').val(null)
fieldValidation.image.validated = false
validateNewLetter()
});
$(".activity-upload-image").on("click", function(e) {
e.preventDefault();
/* We need to setup a Crop control that contains a few parameters
and a method to indicate if the CropController can skip cropping the image.
In this example I am just creating a control on the fly with the expected properties.
However, the controls used by WordPress Admin are api.CroppedImageControl and api.SiteIconControl
*/
var cropControl = {
id: "control-id",
params : {
flex_width : false, // set to true if the width of the cropped image can be different to the width defined here
flex_height : false, // set to true if the height of the cropped image can be different to the height defined here
width : 1100, // set the desired width of the destination image here
height : 460, // set the desired height of the destination image here
}
};
cropControl.mustBeCropped = function(flexW, flexH, dstW, dstH, imgW, imgH) {
// If the width and height are both flexible
// then the user does not need to crop the image.
if ( true === flexW && true === flexH ) {
return false;
}
// If the width is flexible and the cropped image height matches the current image height,
// then the user does not need to crop the image.
if ( true === flexW && dstH === imgH ) {
return false;
}
// If the height is flexible and the cropped image width matches the current image width,
// then the user does not need to crop the image.
if ( true === flexH && dstW === imgW ) {
return false;
}
// If the cropped image width matches the current image width,
// and the cropped image height matches the current image height
// then the user does not need to crop the image.
if ( dstW === imgW && dstH === imgH ) {
return false;
}
// If the destination width is equal to or greater than the cropped image width
// then the user does not need to crop the image...
if ( imgW <= dstW ) {
return false;
}
return true;
};
/* NOTE: Need to set this up every time instead of reusing if already there
as the toolbar button does not get reset when doing the following:
mediaUploader.setState('library');
mediaUploader.open();
*/
console.log('control', cropControl)
mediaUploader = wp.media({
button: {
text: 'Select', // l10n.selectAndCrop,
close: false
},
states: [
new wp.media.controller.Library({
title: 'Select and Crop', // l10n.chooseImage,
library: wp.media.query({ type: 'image' }),
multiple: false,
date: false,
priority: 20,
suggestedWidth: 1100,
suggestedHeight: 460
}),
new wp.media.controller.CustomizeImageCropper({
imgSelectOptions: myTheme_calculateImageSelectOptions,
control: cropControl
})
]
});
mediaUploader.on('cropped', function(croppedImage) {
console.log('cropped', croppedImage)
var url = croppedImage.url,
attachmentId = croppedImage.attachment_id,
w = croppedImage.width,
h = croppedImage.height;
myTheme_setImageFromURL(url, attachmentId, w, h);
});
mediaUploader.on('skippedcrop', function(selection) {
console.log('skipped', selection)
var url = selection.get('url'),
w = selection.get('width'),
h = selection.get('height');
myTheme_setImageFromURL(url, selection.id, w, h);
});
mediaUploader.on("select", function() {
var attachment = mediaUploader.state().get( 'selection' ).first().toJSON();
console.log('select', attachment)
if ( cropControl.params.width === attachment.width
&& cropControl.params.height === attachment.height
&& ! cropControl.params.flex_width
&& ! cropControl.params.flex_height ) {
myTheme_setImageFromAttachment( attachment );
mediaUploader.close();
} else {
console.log(mediaUploader)
mediaUploader.setState( 'cropper' );
}
});
mediaUploader.open();
});```

Related

Overwrite cypress commands to include a wait before they are run

I am trying to overwrite Cypress commands such as click, type and should to include some waiting time before they are executed. My motivation for this is that I want to highlight the areas the test interacts with in the produced video, so in click I would like to say for example: "Display circle where the click will happen, wait 500ms, click, wait 250ms, remove circle".
The wait-part of this of this is what causes me trouble.
Google suggests I do something like this:
Cypress.Commands.overwrite('click', function (originalFN) {
const originalParams = [...arguments].slice(1);
cy.wait(500).then(() => originalFN.apply(originalFN, originalParams));
});
And I think this works for normal clicks(), but it causes the type command to fail entirely saying this: Cypress detected that you returned a promise from a command while also invoking one or more cy commands in that promise.
It seems type() internally calls click in a way that prevents me from using wait() inside click.
Is there any way around this?
I've found a solution in the code of a library to slow down cypress, the key is to overwrite the internal runCommand cypress function. This allows me to do what I want on click and type. Should is still an open question, but not as important. Code below is my function to patch cypress, which I call right before my tests.
export function patchCypressForVideoRecording(cy: any, Cypress: any, speedFactor = 1) {
const colorClick = 'rgba(255, 50, 50, 0.8)';
const colorType = 'rgba(50, 255, 50, 0.8)';
const colorShould = 'rgba(50, 50, 255, 0.8)';
const waitTime = 600;
const highlightArea = (rect: any, clr: string, scale: boolean) => {
const x = Math.round(rect.x);
const y = Math.round(rect.y);
const w = Math.round(rect.width);
const h = Math.round(rect.height);
// cy.window() breaks in commands like click due to promise-inside promises stuff
// this window reference is just there and allows to run synchronous side-effects js without cypress noticing it
const hackWindow = (cy as any).state('window');
hackWindow.eval(`
const time = ${waitTime / speedFactor};
const x = ${x};
const y = ${y};
const highlightElement = document.createElement('div');
highlightElement.style.backgroundColor = '${clr}';
highlightElement.style.position = 'fixed';
highlightElement.style.zIndex = '999';
highlightElement.style['pointer-events'] = 'none';
document.body.appendChild(highlightElement);
const scaleElement = (p) => {
if (${scale}) {
const psq = p;
const scale = (0.1 + ((psq < 0.5 ? (1 - psq) : psq)));
const w = scale * ${w};
const h = scale * ${h};
const wLoss = ${w} - w;
const hLoss = ${h} - h;
const x = ${x} + wLoss / 2;
const y = ${y} + hLoss / 2;
return {x, y, w, h};
} else {
const w = ${w};
const h = ${h};
const x = ${x};
const y = ${y};
return {x, y, w, h};
}
};
const newSize = scaleElement(0);
highlightElement.style.top = newSize.y + 'px';
highlightElement.style.left = newSize.x + 'px';
highlightElement.style.width = newSize.w + "px";
highlightElement.style.height = newSize.h + "px";
const tickSize = 30;
let op = 1;
let prog = 0;
const fadeIv = setInterval(() => {
prog += tickSize;
const p = Math.min(1, prog / time);
let op = 1-(p*0.5);
highlightElement.style.opacity = op + '';
const newSize = scaleElement(p);
highlightElement.style.top = newSize.y + 'px';
highlightElement.style.left = newSize.x + 'px';
highlightElement.style.width = newSize.w + "px";
highlightElement.style.height = newSize.h + "px";
}, tickSize);
setTimeout(() => {
clearInterval(fadeIv);
document.body.removeChild(highlightElement);
}, time);
`);
};
const highlightInteractedElements = (firstParam: any, clr: string, scale: boolean) => {
if (firstParam != null && firstParam.length != null && firstParam.length > 0 && typeof firstParam !== 'string') {
for (let i = 0; i < firstParam.length; i++) {
const elem = firstParam[i];
if (elem != null && 'getBoundingClientRect' in elem && typeof elem['getBoundingClientRect'] === 'function') {
highlightArea(elem.getBoundingClientRect(), clr, scale);
}
}
}
};
// To figure out the element that is clicked/typed in need to wait until
// the selector right before click/type has a subject element
const waitAndDisplay = (x: any, clr: string) => {
if (x.state === 'passed') {
highlightInteractedElements(x.attributes.subject, clr, true);
} else {
if (x.attributes.prev.state === 'queued') {
setTimeout(() => {
waitAndDisplay(x, clr);
}, 15);
} else {
highlightInteractedElements(x.attributes.prev.attributes.subject, clr, true);
}
}
};
const cqueue = (cy as any).queue;
const rc = cqueue.runCommand.bind(cqueue);
cqueue.runCommand = (cmd: any) => {
let delay = 50;
if (cmd.attributes.name === 'click') {
waitAndDisplay(cmd, colorClick);
delay = waitTime / 2;
}
if (cmd.attributes.name === 'type') {
waitAndDisplay(cmd, colorType);
delay = waitTime;
}
return Cypress.Promise.delay(delay / speedFactor)
.then(() => rc(cmd))
.then(() => Cypress.Promise.delay(delay / speedFactor));
};
Cypress.Commands.overwrite('should', function (originalFN: any) {
const originalParams = [...arguments].slice(1);
highlightInteractedElements(originalParams[0], colorShould, false);
return originalFN.apply(originalFN, originalParams);
});
}

Nuxt SSR return loaded image dimensions to server

I'm trying to preview a profile photo on an existing img element my problem is that the new image dimension become undefined outside of the img load function.
How can i pass these dimensions to the server so that i can properly resize the img element?
I've tried using Vuex Store as well to pass the dimensions but with the same undefined results.
I also have a function that listens for resize and after a user changes the window the img is resized properly, however i'm trying to do this without the event trigger. Even when i try to manually trigger the resize event with jQuery it does not resize. I'm under the impression that somehow the dimension of the new img source are not being set properly until a resize even refreshes the dimensions?
<b-img id="profilePhoto" v-bind="profile" :src="this.photo" class="profilePhoto" #change="handleResize()"></b-img>
export default {
data() {
return {
errors:{},
profile: {},
fileDimensions: {},
photo: '',
form: this.$vform({
id: '',
name: '',
email: '',
password: '',
role: '',
bio: '',
photo: ''
})
}
}
}
getPhoto(e) {
let file = e.target.files[0];
if (typeof file !== 'undefined'){
let reader = new FileReader()
let limit = 1024 * 1024 * 2
if (file.size > limit) {
return false;
}
reader.onloadend = (file) => {
this.form.photo = reader.result
this.photo = this.form.photo
}
reader.readAsDataURL(file)
$("<img/>",{
load : function(){
// will return dimensions fine if i use alert here
this.fileDimensions = { width:this.width, height:this.height} ;
},
src : window.URL.createObjectURL(file)
});
// becomes undefined outside of the load functions
var aw = this.fileDimensions.width
var ah = this.fileDimensions.height
var ph = $('.profile').height()
var pw = $('.profile').width()
console.log(this.fileDimensions.width)
if (ah>aw){
this.profile = { width: ph, height: 'auto'}
} else if (aw>ah) {
this.profile = { width: 'auto', height: ph}
} else {
this.profile = { width: ph+10, height: pw+10}
}
}
}
I expect to get the dimensions so i can determine how to set the width and height properties for the img element with its new src however the dimension become undefined.
I figured it out through another post. I had to create a promise. async await in image loading
getDimensions(src){
return new Promise((resolve,reject) => {
let img = new Image()
img.onload = () => resolve({ height: img.height, width: img.width })
img.onerror = reject
img.src = src
})
},
getPhoto(e) {
let file = e.target.files[0];
if (typeof file !== 'undefined'){
let reader = new FileReader()
let limit = 1024 * 1024 * 2
if (file.size > limit) {
return false;
}
reader.onloadend = (file) => {
this.form.photo = reader.result
this.photo = this.form.photo
}
reader.readAsDataURL(file)
this.getDimensions(URL.createObjectURL(file))
.then((dimensions) => {
if (process.client){
var ah = $('.profile').height()
var aw = $('.profile').width()
var ph = dimensions.height
var pw = dimensions.width
if (ph>pw){
this.profile = { width: ah+10, height: 'auto'}
} else if (pw>ph) {
this.profile = { width: 'auto', height: ah+10}
} else {
this.profile = { width: ah+10, height: aw+10}
}
}
})
}
}

Aframe: size of model

When using a model as a source to an entity, say gltf, is there a way we know the original size? Since the scale attribute works on relative size, it seems to be a trial an error to fit the model to our desired size. I tried using the geometry.getComputingBox() of the mesh of the model but it returns null. Wondering if there is a component that is available that lets us specify the scale in absolute terms.
Ah, figured it out.
var model = this.el.object3D;
var box = new THREE.Box3().setFromObject( model );
var size = box.getSize();
gives you the size. then using the above any desired size can be set.
Created a simple component that can be conveniently used
AFRAME.registerComponent('resize', {
schema: {
axis: {
type: 'string',
default: 'x'
},
value: {
type: 'number',
default: 1
}
},
init: function() {
var el = this.el;
var data = this.data;
var model = el.object3D;
el.addEventListener('model-loaded', function(e) {
var box = new THREE.Box3().setFromObject( model );
var size = box.getSize();
var x = size.x;
var y = size.y;
var z = size.z;
if(data.axis === 'x') {
var scale = data.value / x;
}
else if(data.axis === 'y') {
var scale = data.value / y;
}
else {
var scale = data.value / z;
}
el.setAttribute('scale', scale + ' ' + scale + ' ' + scale);
});
}
});
And it can be used as to proportionately resize the model with x axis length as 0.5
<a-entity resize='axis:x; value:0.5' gltf-model='#model`></a-entity>
(This would have come as a comment but as I don't have enough rep points this is coming as an answer.)
I found that the model doesn't have a size directly after the model-loaded event listener so I trigger the rescale from the update method. Funnily enough though if you don't have the model-loaded event listener then the size of the model will be 0 even after the first update is fired.
This is my variant of the above code with the difference being that the dimension is set in meters:
/**
* Scales the object proportionally to a set value given in meters.
*/
AFRAME.registerComponent("natural-size", {
schema: {
width: {
type: "number",
default: undefined, // meters
},
height: {
type: "number",
default: undefined, // meters
},
depth: {
type: "number",
default: undefined, // meters
},
},
init() {
this.el.addEventListener("model-loaded", this.rescale.bind(this));
},
update() {
this.rescale();
},
rescale() {
const el = this.el;
const data = this.data;
const model = el.object3D;
const box = new THREE.Box3().setFromObject(model);
const size = box.getSize();
if (!size.x && !size.y && !size.z) {
return;
}
let scale = 1;
if (data.width) {
scale = data.width / size.x;
} else if (data.height) {
scale = data.height(size.y);
} else if (data.depth) {
scale = data.depth / size.y;
}
el.setAttribute("scale", `${scale} ${scale} ${scale}`);
},
remove() {
this.el.removeEventListener("model-loaded", this.rescale);
},
});
Then:
<a-entity natural-size='width:0.72' gltf-model='#model`></a-entity>
box.getSize has changed, I combined what I found here with what I found in another answer and noticed in the console to produce a more minimalist answer just to determine the size itself of a model:
getDimensions(object3d) {
// e.g., object3d = document.querySelector('#goban').object3D
var box = new THREE.Box3().setFromObject( object3d );
var x = box.max.x - box.min.x
var y = box.max.y - box.min.y
var z = box.max.z - box.min.z
return {x,y,z}
}

Performance issue when zooming (Leaflet 0.7.3)

I'm facing a performance problem with Leaflet (version 0.7.3). I'm working with an OSM map that I use to display a bunch of CircleMarkers linked by decorated Polylines (with arrow pattern every 25px). Loading take a little time but the main problem is that when I zoom the map I start facing severe lag (from the zoom level 16) and, beyond a certain limit (say 18 most of the time), browser just freeze and eventually crash (tested with chrome and firefox). I tried with a bunch of 1,000 linked markers, then I dropped to a set of around 100, but still the same concern... Of course, with 10 markers or less I don't have any problem.
Did you already face a similar trouble? How can I optimize Leaflet performances so that I can use an accurate zoom (beyond level 16) with more than 100 linked CircleMarkers ? I also wonder why performances are dropping so badly when zooming, while marker amount stay the same...
Thank you in advance for your answers,
Lenalys.
Cannot get the PolylineDecorator plugin to work on jsfiddle.
But here is the code that generate markers :
Map initialization :
var map;
function initializeMap(){
"use strict";
var layer;
var layer2;
function layerUrl(key, layer) {
return "http://wxs.ign.fr/" + key
+ "/geoportail/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&"
+ "LAYER=" + layer + "&STYLE=normal&TILEMATRIXSET=PM&"
+ "TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&FORMAT=image%2Fjpeg";
}
layer = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png',
{
attribution: '© OpenStreetMap contributors',
maxZoom: 18
});
layer2 = L.tileLayer(
layerUrl(IGN_AMBIENTE_KEY, "GEOGRAPHICALGRIDSYSTEMS.MAPS"),
{attribution: '© IGN'}
);
var baseMaps = {
"Terrestre": layer,
"Bathymetrique": layer2
};
map = L.map('map', {
layers: [layer],
zoom: 8,
center: [42.152796, 9.139150],
zoomControl: false
});
L.control.layers(baseMaps).addTo(map);
//add zoom control with your options
L.control.zoom({
position:'topright' //topleft
}).addTo(map);
L.control.scale({
position:'bottomleft',
imperial : false
}).addTo(map);
}
Data Sample :
var jsonData ={"12":[{"id_stm_device":"7","individual_name":"cerf3","latitude":"42.657283333333","longitude":"9.42362","temperature":null,"pulse":null,"battery":"20","date_time":"2015-03-17 15:37:12"},
{"id_stm_device":"7","individual_name":"cerf3","latitude":"42.657381666667","longitude":"9.42365","temperature":null,"pulse":null,"battery":"20","date_time":"2015-03-17 16:42:16"},
{"id_stm_device":"7","individual_name":"cerf3","latitude":"42.657381666667","longitude":"9.4236933333333","temperature":null,"pulse":null,"battery":"20","date_time":"2015-03-17 17:47:21"},
{"id_stm_device":"7","individual_name":"cerf3","latitude":"42.657283333333","longitude":"9.4237383333333","temperature":null,"pulse":null,"battery":"20","date_time":"2015-03-17 19:57:23"}],
"13":[{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.61683","longitude":"9.4804633333333","temperature":"17.45","pulse":null,"battery":"80","date_time":"2015-04-08 07:45:20"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.538858333333","longitude":"9.48169","temperature":"14.37","pulse":null,"battery":"80","date_time":"2015-04-08 08:00:29"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.458748333333","longitude":"9.500225","temperature":"14.46","pulse":null,"battery":"80","date_time":"2015-04-08 08:15:49"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.3302","longitude":"9.5374583333333","temperature":"15.19","pulse":null,"battery":"80","date_time":"2015-04-08 08:31:05"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.170133333333","longitude":"9.5272116666667","temperature":"15.48","pulse":null,"battery":"80","date_time":"2015-04-08 08:46:20"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.07959","longitude":"9.47688","temperature":"15.97","pulse":null,"battery":"80","date_time":"2015-04-08 09:01:31"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.076163333333","longitude":"9.4828633333333","temperature":"20.42","pulse":null,"battery":"80","date_time":"2015-04-08 09:16:59"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.07194","longitude":"9.4908866666667","temperature":"17.36","pulse":null,"battery":"80","date_time":"2015-04-08 09:32:17"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.072583333333","longitude":"9.4901516666667","temperature":"17.36","pulse":null,"battery":"80","date_time":"2015-04-08 09:47:32"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.07238","longitude":"9.4904266666667","temperature":"19.38","pulse":null,"battery":"80","date_time":"2015-04-08 10:02:42"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.072298333333","longitude":"9.4904983333333","temperature":"17.46","pulse":null,"battery":"80","date_time":"2015-04-08 10:17:55"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.095093333333","longitude":"9.5148383333333","temperature":"17.47","pulse":null,"battery":"80","date_time":"2015-04-08 10:33:12"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.112881666667","longitude":"9.5133133333333","temperature":"19.3","pulse":null,"battery":"80","date_time":"2015-04-08 10:48:23"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.112875","longitude":"9.513285","temperature":"22.71","pulse":null,"battery":"80","date_time":"2015-04-08 11:03:57"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.141096666667","longitude":"9.5078216666667","temperature":"23.73","pulse":null,"battery":"80","date_time":"2015-04-08 11:19:12"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.282186666667","longitude":"9.5505183333333","temperature":"18.97","pulse":null,"battery":"80","date_time":"2015-04-08 11:34:28"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.405126666667","longitude":"9.531145","temperature":"20.71","pulse":null,"battery":"80","date_time":"2015-04-08 11:49:42"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.482063333333","longitude":"9.480665","temperature":"21.7","pulse":null,"battery":"80","date_time":"2015-04-08 12:05:07"}]}
var oJSON = JSON.parse(jsonData);
var colors = [
"#400080",
"#008000",
"#EC7600",
"#E40341",
"#0D5E5E",
"#919191",
"#FF3C9D",
"#A70A0E",
"#00BFBF",
"#7171FF"
];
var classes = [
"color1",
"color2",
"color3",
"color4",
"color5",
"color6",
"color7",
"color8",
"color9",
"color10"
];
var lastMarkers = [];
var layers = new Array();
var polyline;
var decorator;
window.graphicsDevices = [];
var offsetLatitude = 0.003333;
var offsetLongitude = 0.011666;
Marker instanciation :
function printGPSOnMap(oJSON){
var nbKeys = 0;
for (var key in oJSON) {
nbKeys++;
var classe = classes[(key-1)%classes.length];
var color = colors[(key-1)%colors.length];
var positionInfo = [];
if (oJSON.hasOwnProperty(key)) {
var aInfo = oJSON[key];
var marker;
var latlngs = Array();
var startMarker = lastMarkers[key];
if(startMarker !== undefined && startMarker != null) {
var myIcon = L.divIcon({className: "myCircle "+classe, iconSize : [ 20, 20 ] });
startMarker.setIcon(myIcon);
latlngs.push(startMarker.getLatLng());
}
for(var i = 0; i < aInfo.length; i++) {
var oInfos = aInfo[i];
var sIdIndividual = oInfos["id_individual"];
var sLongitude = oInfos["longitude"];
var sLatitude = oInfos["latitude"];
var sTemperature = oInfos["temperature"];
var sPulse = oInfos["pulse"];
var sBattery = oInfos["battery"];
var sDatetime = oInfos["date_time"];
var sIndividualName = oInfos["individual_name"];
var id_device = oInfos["id_stm_device"];
var popupMsg = "...";
latlngs.push(L.marker([sLatitude,sLongitude]).getLatLng());
marker = new MyCustomMarker([sLatitude,sLongitude], {
icon : L.divIcon({
className : "myCircle "+classe + ((i == aInfo.length-1) ? ' myCircleEnd' : ''),
iconSize : [ 20, 20 ]
})
});
marker.bindPopup(popupMsg, {
showOnMouseOver: true
});
marker.bindLabel(key, {
noHide: true,
direction: 'middle',
offset: [offset[0], offset[1]]
});
positionInfo.push(marker);
}
lastMarkers[key] = marker;
}
if(latlngs.length > 1)
{
polyline = L.polyline(latlngs, {className: classe, weight: 2,opacity: 0.4}).addTo(map);
decorator = L.polylineDecorator(polyline, {
patterns: [
// define a pattern of 10px-wide arrows, repeated every 20px on the line
{offset: 0, repeat: '25px', symbol: new L.Symbol.arrowHead({pixelSize: 10, pathOptions: {fillOpacity:
0.76, color: color, weight: 1}})}
]}).addTo(map);
}
if(!window.graphicsDevices.hasOwnProperty(key))
window.graphicsDevices[key] = [];
for(var i = 0; i < positionInfo.length; i++) {
window.graphicsDevices[key].push(positionInfo[i]);
positionInfo[i].addTo(map);
if(latlngs.length > 1){
window.graphicsDevices[key].push(polyline);
polyline.addTo(map);
window.graphicsDevices[key].push(decorator);
decorator.addTo(map);
}
}
}//foreach key
}
Code for the custom marker :
var MyCustomMarker = L.Marker.extend({
bindPopup: function(htmlContent, options) {
if (options && options.showOnMouseOver) {
// call the super method
L.Marker.prototype.bindPopup.apply(this, [htmlContent, options]);
// unbind the click event
this.off("click", this.openPopup, this);
// bind to mouse over
this.on("mouseover", function(e) {
// get the element that the mouse hovered onto
var target = e.originalEvent.fromElement || e.originalEvent.relatedTarget;
var parent = this._getParent(target, "leaflet-popup");
// check to see if the element is a popup, and if it is this marker's popup
if (parent == this._popup._container)
return true;
// show the popup
this.openPopup();
}, this);
// and mouse out
this.on("mouseout", function(e) {
// get the element that the mouse hovered onto
var target = e.originalEvent.toElement || e.originalEvent.relatedTarget;
// check to see if the element is a popup
if (this._getParent(target, "leaflet-popup")) {
L.DomEvent.on(this._popup._container, "mouseout", this._popupMouseOut, this);
return true;
}
// hide the popup
this.closePopup();
}, this);
}
},
_popupMouseOut: function(e) {
// detach the event
L.DomEvent.off(this._popup, "mouseout", this._popupMouseOut, this);
// get the element that the mouse hovered onto
var target = e.toElement || e.relatedTarget;
// check to see if the element is a popup
if (this._getParent(target, "leaflet-popup"))
return true;
// check to see if the marker was hovered back onto
if (target == this._icon)
return true;
// hide the popup
this.closePopup();
},
_getParent: function(element, className) {
var parent = null;
if(element != null) parent = element.parentNode;
while (parent != null) {
if (parent.className && L.DomUtil.hasClass(parent, className))
return parent;
parent = parent.parentNode;
}
return false;
}
});
Have you gauged performance in canvas mode?
Use L_PREFER_CANVAS = true before initializing your leaflet map container.Might be able to help you possibly.

Is there a 'native' cross platform calendar GUI view example available for Titanium?

As usual, the Q&A section of Appcelerator's developer website isn't being very helpful with this question (unless I'm going blind). It seems that this question is largely asked but never answered.
Is there an example of a calendar view (the GUI - e.g. day, week and month view) that can be deployed to both iOS AND Android? Integration into the built-in calendar (or events) isn't a must (it's not required now but may be in the future).
I've seen stelford and smontgomerie's modules for iOS and pec1985's web view one, however, I'm after one that would result with native (cancels out pec1985's one) GUI objects so that disability assistive technologies are enabled for it.
I Google a lot for showing Month View / Calendar View in my Titanium application but unable to find it out which work on IPhone and Android (Almost all the screens) Both.
After that I tried to implement it by my own and got the good implementation.
To achieve this, Create project which support Android & IPhone.
Open app.js and replace the following code with the existing code and after that Press Command + Shift + F to format the code.
// Taking Screen Width
var screenWidth = 322;
var needToChangeSize = false;
var screenWidthActual = Ti.Platform.displayCaps.platformWidth;
if (Ti.Platform.osname === 'android') {
if (screenWidthActual >= 641) {
screenWidth = screenWidthActual;
needToChangeSize = true;
}
}
// Main Window of the Month View.
var win = Ti.UI.createWindow({
backgroundColor : '#FF000000',
navBarHidden : true
});
// Button at the buttom side
var backButton = Ti.UI.createButton({
bottom : '20dp',
height : '40dp',
width : '200dp'
});
// Previous Button - Tool Bar
var prevMonth = Ti.UI.createButton({
left : '15dp',
width : 'auto',
height : 'auto',
title : '<'
});
// Next Button - Tool Bar
var nextMonth = Ti.UI.createButton({
right : '15dp',
width : 'auto',
height : 'auto',
title : '>'
});
// Month Title - Tool Bar
var monthTitle = Ti.UI.createLabel({
width : '200dp',
height : '24dp',
textAlign : 'center',
color : '#3a4756',
font : {
fontSize : 20,
fontWeight : 'bold'
}
});
// Tool Bar
var toolBar = Ti.UI.createView({
top : '0dp',
width : '322dp',
height : '50dp',
backgroundColor : '#FFFFD800',
layout : 'vertical'
});
// Tool Bar - View which contain Title Prev. & Next Button
var toolBarTitle = Ti.UI.createView({
top : '3dp',
width : '322dp',
height : '24dp'
});
toolBarTitle.add(prevMonth);
toolBarTitle.add(monthTitle);
toolBarTitle.add(nextMonth);
// Tool Bar - Day's
var toolBarDays = Ti.UI.createView({
top : '2dp',
width : '322dp',
height : '22dp',
layout : 'horizontal',
left : '-1dp'
});
toolBarDays.sunday = Ti.UI.createLabel({
left : '0dp',
height : '20dp',
text : 'Sun',
width : '46dp',
textAlign : 'center',
font : {
fontSize : 12,
fontWeight : 'bold'
},
color : '#3a4756'
});
toolBarDays.monday = Ti.UI.createLabel({
left : '0dp',
height : '20dp',
text : 'Mon',
width : '46dp',
textAlign : 'center',
font : {
fontSize : 12,
fontWeight : 'bold'
},
color : '#3a4756'
});
toolBarDays.tuesday = Ti.UI.createLabel({
left : '0dp',
height : '20dp',
text : 'Tue',
width : '46dp',
textAlign : 'center',
font : {
fontSize : 12,
fontWeight : 'bold'
},
color : '#3a4756'
});
toolBarDays.wednesday = Ti.UI.createLabel({
left : '0dp',
height : '20dp',
text : 'Wed',
width : '46dp',
textAlign : 'center',
font : {
fontSize : 12,
fontWeight : 'bold'
},
color : '#3a4756'
});
toolBarDays.thursday = Ti.UI.createLabel({
left : '0dp',
height : '20dp',
text : 'Thu',
width : '46dp',
textAlign : 'center',
font : {
fontSize : 12,
fontWeight : 'bold'
},
color : '#3a4756'
});
toolBarDays.friday = Ti.UI.createLabel({
left : '0dp',
height : '20dp',
text : 'Fri',
width : '46dp',
textAlign : 'center',
font : {
fontSize : 12,
fontWeight : 'bold'
},
color : '#3a4756'
});
toolBarDays.saturday = Ti.UI.createLabel({
left : '0dp',
height : '20dp',
text : 'Sat',
width : '46dp',
textAlign : 'center',
font : {
fontSize : 12,
fontWeight : 'bold'
},
color : '#3a4756'
});
toolBarDays.add(toolBarDays.sunday);
toolBarDays.add(toolBarDays.monday);
toolBarDays.add(toolBarDays.tuesday);
toolBarDays.add(toolBarDays.wednesday);
toolBarDays.add(toolBarDays.thursday);
toolBarDays.add(toolBarDays.friday);
toolBarDays.add(toolBarDays.saturday);
// Adding Tool Bar Title View & Tool Bar Days View
toolBar.add(toolBarTitle);
toolBar.add(toolBarDays);
// Function which create day view template
dayView = function(e) {
var label = Ti.UI.createLabel({
current : e.current,
width : '46dp',
height : '44dp',
backgroundColor : '#FFDCDCDF',
text : e.day,
textAlign : 'center',
color : e.color,
font : {
fontSize : 20,
fontWeight : 'bold'
}
});
return label;
};
monthName = function(e) {
switch(e) {
case 0:
e = 'January';
break;
case 1:
e = 'February';
break;
case 2:
e = 'March';
break;
case 3:
e = 'April';
break;
case 4:
e = 'May';
break;
case 5:
e = 'June';
break;
case 6:
e = 'July';
break;
case 7:
e = 'August';
break;
case 8:
e = 'September';
break;
case 9:
e = 'October';
break;
case 10:
e = 'November';
break;
case 11:
e = 'December';
break;
};
return e;
};
// Calendar Main Function
var calView = function(a, b, c) {
var nameOfMonth = monthName(b);
//create main calendar view
var mainView = Ti.UI.createView({
layout : 'horizontal',
width : '322dp',
height : 'auto',
top : '50dp'
});
//set the time
var daysInMonth = 32 - new Date(a, b, 32).getDate();
var dayOfMonth = new Date(a, b, c).getDate();
var dayOfWeek = new Date(a, b, 1).getDay();
var daysInLastMonth = 32 - new Date(a, b - 1, 32).getDate();
var daysInNextMonth = (new Date(a, b, daysInMonth).getDay()) - 6;
//set initial day number
var dayNumber = daysInLastMonth - dayOfWeek + 1;
//get last month's days
for ( i = 0; i < dayOfWeek; i++) {
mainView.add(new dayView({
day : dayNumber,
color : '#8e959f',
current : 'no',
dayOfMonth : ''
}));
dayNumber++;
};
// reset day number for current month
dayNumber = 1;
//get this month's days
for ( i = 0; i < daysInMonth; i++) {
var newDay = new dayView({
day : dayNumber,
color : '#3a4756',
current : 'yes',
dayOfMonth : dayOfMonth
});
mainView.add(newDay);
if (newDay.text == dayOfMonth) {
newDay.color = 'white';
// newDay.backgroundImage='../libraries/calendar/pngs/monthdaytiletoday_selected.png';
newDay.backgroundColor = '#FFFFF000';
var oldDay = newDay;
}
dayNumber++;
};
dayNumber = 1;
//get remaining month's days
for ( i = 0; i > daysInNextMonth; i--) {
mainView.add(new dayView({
day : dayNumber,
color : '#8e959f',
current : 'no',
dayOfMonth : ''
}));
dayNumber++;
};
// this is the new "clicker" function, although it doesn't have a name anymore, it just is.
mainView.addEventListener('click', function(e) {
if (e.source.current == 'yes') {
// reset last day selected
if (oldDay.text == dayOfMonth) {
oldDay.color = 'white';
// oldDay.backgroundImage='../libraries/calendar/pngs/monthdaytiletoday.png';
oldDay.backgroundColor = '#FFFFF000';
} else {
oldDay.color = '#3a4756';
// oldDay.backgroundImage='../libraries/calendar/pngs/monthdaytile-Decoded.png';
oldDay.backgroundColor = '#FFDCDCDF'
}
oldDay.backgroundPaddingLeft = '0dp';
oldDay.backgroundPaddingBottom = '0dp';
// set window title with day selected, for testing purposes only
backButton.title = nameOfMonth + ' ' + e.source.text + ', ' + a;
// set characteristic of the day selected
if (e.source.text == dayOfMonth) {
// e.source.backgroundImage='../libraries/calendar/pngs/monthdaytiletoday_selected.png';
e.source.backgroundColor = '#FFFF00FF';
} else {
// e.source.backgroundImage='../libraries/calendar/pngs/monthdaytile_selected.png';
e.source.backgroundColor = '#FFFF0000';
}
e.source.backgroundPaddingLeft = '1dp';
e.source.backgroundPaddingBottom = '1dp';
e.source.color = 'white';
//this day becomes old :(
oldDay = e.source;
}
});
return mainView;
};
// what's today's date?
var setDate = new Date();
a = setDate.getFullYear();
b = setDate.getMonth();
c = setDate.getDate();
// add the three calendar views to the window for changing calendars with animation later
var prevCalendarView = null;
if (b == 0) {
prevCalendarView = calView(a - 1, 11, c);
} else {
prevCalendarView = calView(a, b - 1, c);
}
prevCalendarView.left = (screenWidth * -1) + 'dp';
var nextCalendarView = null;
if (b == 0) {
nextCalendarView = calView(a + 1, 0, c);
} else {
nextCalendarView = calView(a, b + 1, c);
}
nextCalendarView.left = screenWidth + 'dp';
var thisCalendarView = calView(a, b, c);
if (needToChangeSize == false) {
thisCalendarView.left = '-1dp';
}
monthTitle.text = monthName(b) + ' ' + a;
backButton.title = monthName(b) + ' ' + c + ', ' + a;
// add everything to the window
win.add(toolBar);
win.add(thisCalendarView);
win.add(nextCalendarView);
win.add(prevCalendarView);
win.add(backButton);
// yeah, open the window, why not?
win.open({
modal : true
});
var slideNext = Titanium.UI.createAnimation({
// left : '-322',
duration : 500
});
slideNext.left = (screenWidth * -1);
var slideReset = Titanium.UI.createAnimation({
// left : '-1',
duration : 500
});
if (needToChangeSize == false) {
slideReset.left = '-1';
} else {
slideReset.left = ((screenWidth - 644) / 2);
}
var slidePrev = Titanium.UI.createAnimation({
// left : '322',
duration : 500
});
slidePrev.left = screenWidth;
// Next Month Click Event
nextMonth.addEventListener('click', function() {
if (b == 11) {
b = 0;
a++;
} else {
b++;
}
thisCalendarView.animate(slideNext);
nextCalendarView.animate(slideReset);
setTimeout(function() {
thisCalendarView.left = (screenWidth * -1) + 'dp';
if (needToChangeSize == false) {
nextCalendarView.left = '-1dp';
} else {
nextCalendarView.left = ((screenWidth - 644) / 2);
}
prevCalendarView = thisCalendarView;
thisCalendarView = nextCalendarView;
if (b == 11) {
nextCalendarView = calView(a + 1, 0, c);
} else {
nextCalendarView = calView(a, b + 1, c);
}
monthTitle.text = monthName(b) + ' ' + a;
nextCalendarView.left = screenWidth + 'dp';
win.add(nextCalendarView);
}, 500);
});
// Previous Month Click Event
prevMonth.addEventListener('click', function() {
if (b == 0) {
b = 11;
a--;
} else {
b--;
}
thisCalendarView.animate(slidePrev);
prevCalendarView.animate(slideReset);
setTimeout(function() {
thisCalendarView.left = screenWidth + 'dp';
if (needToChangeSize == false) {
prevCalendarView.left = '-1dp';
} else {
prevCalendarView.left = ((screenWidth - 644) / 2);
}
nextCalendarView = thisCalendarView;
thisCalendarView = prevCalendarView;
if (b == 0) {
prevCalendarView = calView(a - 1, 11, c);
} else {
prevCalendarView = calView(a, b - 1, c);
}
monthTitle.text = monthName(b) + ' ' + a;
prevCalendarView.left = (screenWidth * -1) + 'dp';
win.add(prevCalendarView);
}, 500);
});
Tested Environment
Titanium Studio - 2.1.2.201208301612
Titanium SDK - 2.1.0.GA
Android - 2.2 Emulator
iOS Version - 5.0
Reference - http://titaniumexplorers.blogspot.in/2012/09/titanium-calendar-month-view.html
no.
the implementations of the calendar UI on the two platforms is so drastically different and complex that the probability of finding one implementation to meet all of your requirements is highly unlikely.
You are not going blind, your requirements are just very specific.

Resources