I use the background.js script to create new windows, but when I set the bounds when using the chrome.app.window.create method and pass it parameters for the CreateWindowOptions object the are used the first time, but ignored whenever I relaunch the app.
On OSX, it seems that calling the methods setPosition and setSize after the create callback on the window's outerBounds object the app will then respond to the bounds I try to set. However, this doesn't appear to work on Windows 10 at all.
It seems somehow the windows I create persist when the app is closed. I'm not sure how to clear them, especially if the user closes the app uses the menu or OS to quit. Is there some way to make sure to destroy or clear these windows, or force the Chrome App to set the bounds I set without reverting to some previous state?
The windows I create are running PixiJS if that makes any difference. I do call a destroy method on the windows when the close event is triggered, but that has no affect.
Here's my background script:
(function( global ) {
"use strict";
/* ----- VARS ----- */
var displayInfo;
var isMultiDisplay;
var controlDisplayIndex;
var controlBounds;
var controlLoaded;
var controlWindow;
var gameDisplayIndex;
var gameBounds;
var gameLoaded;
var gameWindow;
/* ----- CONSTANTS ----- */
var CONTROL_WIDTH = 1920;
var CONTROL_HEIGHT = 1080;
var GAME_WIDTH = 2160;
var GAME_HEIGHT = 3840;
var FRAME_OPTIONS = {
type: "none"
};
/* ----- FUNCTIONS ----- */
function init() {
console.log( "background.js: init" );
console.info( "background.js: ", chrome.app.window.getAll() );
isMultiDisplay = false;
controlLoaded = false;
gameLoaded = false;
loadDisplayInfo();
};
function loadDisplayInfo() {
console.log( "background.js: loadDisplayInfo" );
chrome.system.display.getInfo( onDisplayInfo );
};
function createWindows() {
console.log( "background.js: createWindows" );
isMultiDisplay = ( displayInfo.length > 0 );
if ( isMultiDisplay ) {
var i = 0;
var length = 2;
for ( i; i < length; i++ ) {
if ( displayInfo[i].isPrimary )
controlDisplayIndex = i;
else
gameDisplayIndex = i;
}
gameBounds = {
height: displayInfo[ gameDisplayIndex ].workArea.height,
left: displayInfo[ gameDisplayIndex ].workArea.left,
top: displayInfo[ gameDisplayIndex ].workArea.top,
width: displayInfo[ gameDisplayIndex ].workArea.width
};
controlBounds = {
height: displayInfo[ controlDisplayIndex ].workArea.height,
left: displayInfo[ controlDisplayIndex ].workArea.left,
top: displayInfo[ controlDisplayIndex ].workArea.top,
width: displayInfo[ controlDisplayIndex ].workArea.width
};
} else {
// This assumes single monitor is in landscape.
gameBounds = {
top: displayInfo[0].workArea.top,
left: displayInfo[0].workArea.left,
height: displayInfo[0].workArea.height,
width: Math.floor( displayInfo[0].workArea.height * GAME_WIDTH / GAME_HEIGHT )
};
controlBounds = {
top: displayInfo[0].workArea.top,
left: displayInfo[0].workArea.left + gameBounds.width,
width: gameBounds.width,
height: Math.floor( gameBounds.width * CONTROL_HEIGHT / CONTROL_WIDTH )
};
}
console.info( "Game Bounds:", gameDisplayIndex, gameBounds.left, gameBounds.top, gameBounds.width, gameBounds.height );
console.info( "Control Bounds:", controlDisplayIndex, controlBounds.left, controlBounds.top, controlBounds.width, controlBounds.height );
var state = ( isMultiDisplay ) ? "fullscreen" : "normal";
// Game
chrome.app.window.create( "window-game.html", {
id: "gameWindow",
bounds: gameBounds,
frame: FRAME_OPTIONS,
resizable: true,
state: state
}, onGameWindowCreated );
// Control
chrome.app.window.create( "window-control.html", {
id: "controlWindow",
bounds: controlBounds,
frame: FRAME_OPTIONS,
resizable: true,
state: state
}, onControlWindowCreated );
};
/* ----- EVENT LISTENERS ----- */
function onLaunched() {
console.log( "background.js: onLaunched" );
init();
};
function onDisplayInfo( info ) {
console.log( "background.js: onDisplayInfo" );
displayInfo = info;
createWindows();
};
function onControlWindowCreated( obj ) {
console.log( "background.js: onControlWindowCreated", obj.id );
controlLoaded = true;
controlWindow = obj;
controlWindow.outerBounds.setPosition( controlBounds.left, controlBounds.top );
controlWindow.outerBounds.setSize( controlBounds.width, controlBounds.height );
if ( isMultiDisplay ) controlWindow.fullscreen();
//console.info( controlWindow.innerBounds.width, controlWindow.innerBounds.height );
//console.info( controlWindow.outerBounds.width, controlWindow.outerBounds.height );
controlWindow.onClosed.addListener( onControlWindowClosed );
};
function onControlWindowClosed() {
console.log( "background.js: onControlWindowClosed" );
controlWindow.onClosed.removeListener( onControlWindowClosed );
controlWindow.destroy();
controlWindow = undefined;
};
function onGameWindowCreated( obj ) {
console.log( "background.js: onGameWindowCreated", obj.id );
gameLoaded = true;
gameWindow = obj;
gameWindow.outerBounds.setPosition( gameBounds.left, gameBounds.top );
gameWindow.outerBounds.setSize( gameBounds.width, gameBounds.height );
if ( isMultiDisplay ) gameWindow.fullscreen();
//console.info( gameWindow.innerBounds.width, gameWindow.innerBounds.height );
//console.info( gameWindow.outerBounds.width, gameWindow.outerBounds.height );
gameWindow.onClosed.addListener( onGameWindowClosed );
};
function onGameWindowClosed() {
console.log( "background.js: onGameWindowClosed" );
gameWindow.onClosed.removeListener( onGameWindowClosed );
gameWindow.destroy();
gameWindow = undefined;
};
/* ----- CALL ----- */
chrome.app.runtime.onLaunched.addListener( onLaunched );
}( this ));
Disclaimer: I know that Chrome Apps are being discontinued, but due to project timings I've got to finish this app off and then look to port to Electron or alternative.
When you create a window and set the id property in the CreateWindowOptions object, the last known bounds for that window will be saved by the Chrome Apps platform and used the next a window is created with that same id.
Here's the description from the docs on the id property:
Id to identify the window. This will be used to remember the size and
position of the window and restore that geometry when a window with
the same id is later opened. If a window with a given id is created
while another window with the same id already exists, the currently
opened window will be focused instead of creating a new window.
And under the description of the outerBounds property:
Used to specify the initial position, initial size and constraints of
the window (including window decorations such as the title bar and
frame). If an id is also specified and a window with a matching id has
been shown before, the remembered bounds will be used instead.
So, the solution is to omit the id property. If you, like myself, need to do further things with that window in your script, then use the create window callback to assign a variable to that's function return parameter.
For example:
var myWindow;
chrome.app.window.create( "window.html", options, function( obj ) {
myWindow = obj;
// Do Stuff with myWindow
} );
I guess even attempting to set the size and position of the window after the callback isn't full-proof as is evident when I tried to do some under Windows 10.
The only remaining unknown to me is where is Google Chrome Apps storing all this window data when you give windows an id? And is there a way to clear that (other than uninstalling the app)?
Related
I'm building an Adobe AIR application in HTML/JavaScript that will use two windows, once of which will be display on the largest screen available (if available).
The code is as follows:
function showProjector(){
if(air.Screen.screens.length > 1) {
if(htmlLoader) {
// Projector is already shown
} else {
// Create a new window that will become the projector screen
var options = new air.NativeWindowInitOptions();
options.systemChrome = air.NativeWindowSystemChrome.NONE;
options.transparent = true;
options.type= air.NativeWindowType.LIGHTWEIGHT;
var htmlLoader = air.HTMLLoader.createRootWindow(false, options, false);
htmlLoader.window.nativeWindow.visible = true;
// Add content to new window
htmlLoader.load(new air.URLRequest('Projector.html'));
// Make the window appear on the biggest screen
htmlLoader.bounds = air.Screen.screens[1];
// Make it full screen
htmlLoader.stage.displayState = runtime.flash.display.StageDisplayState.FULL_SCREEN_INTERACTIVE;
}
} else {
// Show error that you need a projector screen
alert('You need a projector screen');
}
}
I've handled the parts checking if more than one screen is available and if the projector is already being displayed. But need to find out which is the biggest screen and make sure that the current window DOES not move to it, and the new htmlLoader one DOES move it to.
How can I do this?
Finding the largest screen:
var largestScreen = null;
for( var index = 0; index < Air.Screen.screens.length; ++index) {
var currentScreen = Air.Screen.screens[index];
if( largestScreen == null ){
largestScreen = currentScreen;
}
else{
//Defining largest screen as biggest total area.
var currentArea = currentScreen.bounds.width * currentScreen.bounds.height;
var largestArea = largestScreen.bounds.width * largestScreen.bounds.height;
if(currentArea > largestArea) {
largestScreen = currentScreen;
}
}
}
Setting the projector window to the largest screen needs to happen on creation
var htmlLoader = air.HTMLLoader.createRootWindow(false, options, false, largestScreen.bounds);
See reference:
flash.display.NativeWindow
flash.html.HTMLLoader
flash.display.Screen
I've built this isotope module - but I want to enhance it - so it snaps to the bottom, and can take updates from a json request. Also if a change occurs - like an online user views a profile - to make the change to a personal reference on the user's page.
http://jsfiddle.net/CXqM2/26/
Here is the current code
var $container = $( '#isotope' ),
// #see {#link http://fgnass.github.io/spin.js}
spinJsConfiguration = {
lines: 5, // The number of lines to draw
length: 3, // The length of each line
width: 2, // The line thickness
radius: 6, // The radius of the inner circle
color: '#666' // #rgb or #rrggbb or array of colors
};
// initialize isotope
// prevent "First item breaks Masonry layout" issue
// #see {#link http://isotope.metafizzy.co/docs/help.html#first_item_breaks_masonry_layout}
$container.isotope({
masonry: {
columnWidth: 30
}
});
// handle click events
$container.on( 'click', '.user', function( event ) {
var $this = $( this );
event.preventDefault();
// if not already open, do so
if ( !$this.hasClass( 'open' ) ){
var $openItem = $container.find( '.open' );
// if any, close currently open items
if ( $openItem.length ) {
closeItem( $openItem );
}
openItem( $this );
}
});
$container.on( 'click', '.close', function( event ) {
event.stopPropagation();
closeItem( $( this ).closest( '.user' ) );
});
function openItem( $item ) {
var $image = $item.find( '.user-image' );
$item.addClass( 'loading' ).spin( spinJsConfiguration );
// #todo we should only replace the image once
$image.attr( 'src', $image.data( 'src-large' ) );
// at least for the sake of this demo we can use the "imagesLoaded" plugin contained within
// Isotope to determine if the large version of the user image has loaded
// #todo Isotope v1 contains an outdated version of the "imagesLoaded" plugin - please use the current one
// #see {#link https://github.com/desandro/imagesloaded}
$item.imagesLoaded( function() {
$item.spin( false ).removeClass( 'loading' ).addClass( 'open' );
$container.addClass( 'item-open' ).isotope( 'reLayout' );
$item.append( '<div class="close">×</div>' );
});
}
function closeItem( $item ) {
$item.removeClass( 'open' ).find( '.close' ).remove();
$container.removeClass( 'item-open' ).isotope( 'reLayout' );
}
This demo
http://jsfiddle.net/CXqM2/85/
Is able to update the isotope with json data. I am able to re-populate the list with new json data updates.
I essentially want items no longer existing to fade off - remove
new items to be added on - add/insert
any priority updates like - user sent you a message to auto open on this for the user. How do I trigger this?
here is the code that repopulates every 10 seconds
getUpdate: function(){
function getRandomInt (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
var that = this;
window.setInterval(function(){
console.log("new data time");
var rand = getRandomInt (0, 1);
that.populateIsotope(data[rand].stream);
//_re_invoke isotope
$container.isotope('reLayout')
},10000);
}
LATEST CODE ******** http://jsfiddle.net/CXqM2/209/ *******
This example will rely on some development on the backend to provide a priority element - using the notification date and the notification id to help identify the priority element in the stream. I've added the date sorting to the isotope code.
getSortData: {
date: function ($elem) {
var dates = $elem.attr('data-user-notification-date');
dateArray = dates.split('/'),
year = dateArray[2].substr(0, 4),
month = dateArray[1],
day = dateArray[0];
timeArray = dates.split(':'),
hours = timeArray[0].slice(-2),
minutes = timeArray[1],
seconds = timeArray[2];
return new Date(year, month, day, hours, minutes, seconds);
}
}
The other day I stumbled upon with this example of a Javascript audio recorder:
http://webaudiodemos.appspot.com/AudioRecorder/index.html
Which I ended up using for implementing my own. The problem I'm having is that in this file:
var audioContext = new webkitAudioContext();
var audioInput = null,
realAudioInput = null,
inputPoint = null,
audioRecorder = null;
var rafID = null;
var analyserContext = null;
var canvasWidth, canvasHeight;
var recIndex = 0;
/* TODO:
- offer mono option
- "Monitor input" switch
*/
function saveAudio() {
audioRecorder.exportWAV( doneEncoding );
}
function drawWave( buffers ) {
var canvas = document.getElementById( "wavedisplay" );
drawBuffer( canvas.width, canvas.height, canvas.getContext('2d'), buffers[0] );
}
function doneEncoding( blob ) {
Recorder.forceDownload( blob, "myRecording" + ((recIndex<10)?"0":"") + recIndex + ".wav" );
recIndex++;
}
function toggleRecording( e ) {
if (e.classList.contains("recording")) {
// stop recording
audioRecorder.stop();
e.classList.remove("recording");
audioRecorder.getBuffers( drawWave );
} else {
// start recording
if (!audioRecorder)
return;
e.classList.add("recording");
audioRecorder.clear();
audioRecorder.record();
}
}
// this is a helper function to force mono for some interfaces that return a stereo channel for a mono source.
// it's not currently used, but probably will be in the future.
function convertToMono( input ) {
var splitter = audioContext.createChannelSplitter(2);
var merger = audioContext.createChannelMerger(2);
input.connect( splitter );
splitter.connect( merger, 0, 0 );
splitter.connect( merger, 0, 1 );
return merger;
}
function toggleMono() {
if (audioInput != realAudioInput) {
audioInput.disconnect();
realAudioInput.disconnect();
audioInput = realAudioInput;
} else {
realAudioInput.disconnect();
audioInput = convertToMono( realAudioInput );
}
audioInput.connect(inputPoint);
}
function cancelAnalyserUpdates() {
window.webkitCancelAnimationFrame( rafID );
rafID = null;
}
function updateAnalysers(time) {
if (!analyserContext) {
var canvas = document.getElementById("analyser");
canvasWidth = canvas.width;
canvasHeight = canvas.height;
analyserContext = canvas.getContext('2d');
}
// analyzer draw code here
{
var SPACING = 3;
var BAR_WIDTH = 1;
var numBars = Math.round(canvasWidth / SPACING);
var freqByteData = new Uint8Array(analyserNode.frequencyBinCount);
analyserNode.getByteFrequencyData(freqByteData);
analyserContext.clearRect(0, 0, canvasWidth, canvasHeight);
analyserContext.fillStyle = '#F6D565';
analyserContext.lineCap = 'round';
var multiplier = analyserNode.frequencyBinCount / numBars;
// Draw rectangle for each frequency bin.
for (var i = 0; i < numBars; ++i) {
var magnitude = 0;
var offset = Math.floor( i * multiplier );
// gotta sum/average the block, or we miss narrow-bandwidth spikes
for (var j = 0; j< multiplier; j++)
magnitude += freqByteData[offset + j];
magnitude = magnitude / multiplier;
var magnitude2 = freqByteData[i * multiplier];
analyserContext.fillStyle = "hsl( " + Math.round((i*360)/numBars) + ", 100%, 50%)";
analyserContext.fillRect(i * SPACING, canvasHeight, BAR_WIDTH, -magnitude);
}
}
rafID = window.webkitRequestAnimationFrame( updateAnalysers );
}
function gotStream(stream) {
// "inputPoint" is the node to connect your output recording to.
inputPoint = audioContext.createGainNode();
// Create an AudioNode from the stream.
realAudioInput = audioContext.createMediaStreamSource(stream);
audioInput = realAudioInput;
audioInput.connect(inputPoint);
// audioInput = convertToMono( input );
analyserNode = audioContext.createAnalyser();
analyserNode.fftSize = 2048;
inputPoint.connect( analyserNode );
audioRecorder = new Recorder( inputPoint );
zeroGain = audioContext.createGainNode();
zeroGain.gain.value = 0.0;
inputPoint.connect( zeroGain );
zeroGain.connect( audioContext.destination );
updateAnalysers();
}
function initAudio() {
if (!navigator.webkitGetUserMedia)
return(alert("Error: getUserMedia not supported!"));
navigator.webkitGetUserMedia({audio:true}, gotStream, function(e) {
alert('Error getting audio');
console.log(e);
});
}
window.addEventListener('load', initAudio );
As you might be able to see, the initAudio() function (the one wich ask the user for permission to use his/her microphone) is called inmediately when the page is loaded (read the last line) with this method:
window.addEventListener('load', initAudio );
Now, I have this code in the HTML:
<script type="text/javascript" >
$(function() {
$("#recbutton").on("click", function() {
$("#entrance").hide();
$("#live").fadeIn("slow");
toggleRecording(this);
$(this).toggle();
return $("#stopbutton").toggle();
});
return $("#stopbutton").on("click", function() {
audioRecorder.stop();
$(this).toggle();
$("#recbutton").toggle();
$("#live").hide();
return $("#entrance").fadeIn("slow");
});
});
</script>
And as you can see, I call the toggleRecording(this) function (the one wich starts the recording process) only after the #recbutton is pressed. Now, everything works fine with this code BUT, the user gets prompted for microphone permission as soon as the page is loaded and I want to ask them for permission to use the microphone ONLY AFTER they clicked the #recbutton Do you understand me? I tought that if I remove the last line of the first file:
window.addEventListener('load', initAudio );
and modify my embedded script like this:
<script type="text/javascript" >
$(function() {
$("#recbutton").on("click", function() {
$("#entrance").hide();
$("#live").fadeIn("slow");
initAudio();
toggleRecording(this);
$(this).toggle();
return $("#stopbutton").toggle();
});
return $("#stopbutton").on("click", function() {
audioRecorder.stop();
$(this).toggle();
$("#recbutton").toggle();
$("#live").hide();
return $("#entrance").fadeIn("slow");
});
});
</script>
I might be able to achieve what I wanted, and actually I am, the user doesn't get prompted for his/her microphone until they click the #recbutton. The problem is, the audio never get's recorded, when you try to download it, the resulting WAV it is empty.
How can I fix this?
My project's code is at: https://github.com/Jmlevick/html-recorder
No, your problem is that getUserMedia() has an asynchronous callback (gotMedia()); you need to have the rest of your code logic in the startbutton call (the toggleRecording bit, in particular) inside that callback, because right now it's getting executed before getUserMedia returns (and sets up the audio nodes).
I found an elegant & easy solution for this (or at least I see it that way):
What I did was toss "main.js" and "recorder.js" inside a getScript call that is executed only when a certain button (#button1) is clicked by the user... These scripts do not get loaded with the webpage itself until the button it's pressed, but we need some more nifty tricks to make it work the way I described and wanted above:
in main.js, I changed:
window.addEventListener('load', initAudio );
for:
window.addEventListener('click', initAudio );
So when the scripts are loaded into the page with getScript the "main.js" file now listens for a click event in the webpage to ask the user for the microphone. Next, I had to create a hidden button (#button2) on the page wich is fakely clicked by jQuery exactly right after the scripts are loaded on the page, so it triggers the "ask for microphone permisson" event and then, just below that line of code wich generates the fake click I added:
window.removeEventListener("click", initAudio, false);
so the "workflow" for this trick ends up as follows:
User presses a button wich loads the necesary js files into the page with getScript, it's worth mentioning that now the "main.js" file listens for a click event on the window instead of a load one.
We have a hidden button wich is "fakely clicked" by jQuery just in the moment you click the first one, so it triggers the permisson event for the user.
Once this event is triggered, the click event listener is removed from the window, so it never fires the "ask for permisson" event again when the user clicks anywhere on the page.
And basically that's all folks! :) now when the user goes into the page he/she never get asked for microphone permisson until they click a "Rec" button on the page just as I wanted. With one click of the user we do 3 things in jQuery, but for the user it seems like nothing happened other that the "microphone permisson message" appearing on the screen instantly right after they click the "Rec" Button.
I am writing my first custom function/addon for jQuery, it's been a lot of fun. basically it will load a bunch of images from an array and create a slider to enable you to pick one from the list and display it as the main image.
in my options I have added an onClick event, but I want to be able to pass variables to this event for the end user to set in the options, such as the image src, so when you click an image the optional onClick function fires and the end user could then return the src of the image clicked, he is my code for a better example, it may be a tad dirty as I haven't cleaned it and its full of code that needs polishing but it works.
(function($){
$.fn.imagePicker = function( options ) {
var defaults = {
width: 398,
height: 230,
imageArray: ["/img/news/emailpi.jpg", "/img/news/debit.jpg", "/img/news/aim-high.jpg", "/img/news/Koala.jpg", "/img/news/170217_get_connected.jpg",
"/img/news/10.jpg", "/img/news/1000864_street_variations__doors_2.jpg", "/img/news/1005134_ear_defenders.jpg", "/img/news/1080177_magnifying_glass.jpg", "/img/news/160.jpg",
"/img/news/161.jpg", "/img/news/162.jpg", "/img/news/163.jpg", "/img/news/2012.jpg", "/img/news/48.jpg",
"/img/news/8.jpg", "/img/news/12.jpg", "/img/news/ski.jpg", "/img/news/storm.jpg", "/img/news/tax-img.jpg",],
show: 10,
thumb: {
width:70,
height: 60,
borderWidth: 1,
borderColour: "#CCC",
borderStyle: "solid",
padding:3
},
mainImage: {
width: 150,
height:200,
borderColour:"#CCC",
borderStyle:"solid",
padding:3
},
leftScroll: 50,
rightScroll: 50,
interval: 5,
onClick: function(){},
onScrollLeft:function(){},
onScrollRight:function(){},
debug: false
};
var options = $.extend( true, defaults, options );
//Preload the images in the array
ip_preloader( options.imageArray )
$(this).css({width:options.width, height:options.height, overflow:'hidden'});
$(this).html( "<div class='ip-main-images' style='text-align:center'></div>\n\
<div class='ip-image-menu' style='position:relative; height:"+(options.thumb.height+5)+"px; width:"+( ((options.thumb.width + options.thumb.padding + ( options.thumb.borderWidth*2 ))*options.imageArray.length) )+"px; overflow:hidden;'>\n\
<div class='ip-menu-images-overlay' style='width:"+options.width+"px; position:absolute; top:0;'>\n\
<div class='ip-left-hotspot' style='background:url(/admin_v2/css/images/white_trans_10x10.png);position:absolute; left:0; top:0; width:"+options.leftScroll+"px; height:"+( options.thumb.height+( options.thumb.borderWidth*2 ) )+"px; z-index:99;'></div>\n\
<div class='ip-right-hotspot' style='background:url(/admin_v2/css/images/white_trans_10x10.png);position:absolute; right:0; top:0; width:"+options.rightScroll+"px; height:"+( options.thumb.height+( options.thumb.borderWidth*2 ) )+"px; z-index:99;'></div>\n\
</div>\n\
<div class='ip-menu-images' style='position:relative; top:0;'>\n\
</div>\n\
</div>\n\
<div class='ip-mouse-pos'></div>" );
var container = $(this);
var menu = $(this).find( ".ip-menu-images" );
var main = $(this).find( ".ip-main-images" );
main.html( "<img src='"+options.imageArray[0]+"' width='"+options.mainImage.height+"' height='"+options.mainImage.width+"' style='margin:0 auto; width:"+options.mainImage.width+"px' />" );
for( i=0; i<options.imageArray.length; i++ ){
menu.append( "<img src='"+options.imageArray[i]+"' style='float:left; margin-right:"+options.thumb.padding+"px; border:"+options.thumb.borderWidth+"px "+options.thumb.borderStyle+" "+options.thumb.borderColour+";' alt='' class='' width='"+options.thumb.width+"' height='"+options.thumb.height+"' />" );
}
$(".ip-image-menu img").live("click", function( ){
var src = $(this).attr("src");
$(".ip-main-images img").attr("src", src);
options.onClick( src );
});
var leftpos = 0;
$(".ip-left-hotspot").bind('mouseenter', function() {
this.iid = setInterval(function() {
if( leftpos != options.leftScroll ){
leftpos ++;
menu.css("left", leftpos);
options.onScrollLeft();
}
if(options.debug)$(".ip-mouse-pos").html( "LeftPos: "+menu.css("left") );
}, options.interval);
}).bind('mouseleave', function(){
this.iid && clearInterval(this.iid);
});
$(".ip-right-hotspot").bind('mouseenter', function() {
this.iids = setInterval(function() {
if( leftpos != "-"+menu.width()){
leftpos --;
menu.css("left", leftpos);
options.onScrollRight();
}
if(options.debug)$(".ip-mouse-pos").html( "LeftPos: "+menu.css("left") );
}, options.interval);
}).bind('mouseleave', function(){
this.iids && clearInterval(this.iids);
});
function ip_preloader( imagesArray )
{
// counter
var i = 0;
// create object
imageObj = new Image();
// start preloading
for(i=0; i<=3; i++)
{
imageObj.src=imagesArray[i];
}
}
; };
})(jQuery);
if you do a find for ("src") will jump you to the bit of code where I call options.onClick and I just want to pass the src to that event so the end user has access to it, should they want to populate a hidden form field with the image src etc. And also so I know how it works, as I am struggling to google for it without the correct terminology. (am not even sure if what I am trying to do is called "an event")
You could use the following:
if(typeof(options.onClick) == 'function'){
options.onClick(x, y, z);
}
You can just give the parameters you want to the function. JavaScript doesn't care how many arguments you give the function. You subscriber just neest to specify the parameters in his handler.
This example shows that it works.
var onclick;
onclick = function(a){
alert(a);
};
onclick('Hellow world', 'I am useless');
So this should work as well:
$('#myElement').imagePicker({
onclick: function(src){
alert(src);
}
});
Maybe you could make your example a bit smaller. This bulk of code is a little bit to much.
This is a loose example but you could allow the user to pass function into the constructor, in this example I am passing an object of callbacks but afaik it would amount to the same thing
var a = "xyz";
var Foo = function(args, callbacks) {
var a = "abc";
this.bar = callbacks.bar(a);
};
var foo = new Foo({}, { bar: function(a){ alert(a) } });
foo.bar(); // will alert "abc" NOT "xyz"
here is a demo
Ok so i'm learning to write plugins and the jQuery site for plugin authoring is damn confusing. I'm really trying to figure out how to add methods to my plugin that will fire based on a browser event. So basically I have a plugin that builds out a swipeable slide show for a mobile device. When the device is rotated I need it to rebuild the slideshow for the new dimensions. Right now I have it so it builds it the first time but I can't (a) figure out to add methods for different functions and (b) bind events to those specific methods. here's my code.
(function( $ ){
$.fn.jCarousel = function( options ) {
var empty = {}
var defaults = {
height: $(window).height(),
width: $(window).width(),
count: 1,
amount: $('.swiper', this).length,
thisone:this,
};
var options = $.extend(empty, defaults, options);
return this.each(function() {
options.thisone = this;
amount = $('.swiper', this).length;
holdWidth = options.width * options.amount;
$('.swiper', this).height(options.height);
$(this).height(options.height);
$('.swiper', this).width(options.width);
$(this).width(holdWidth);
$('.swipe_slide',this).width(holdWidth);
//==================================================================================================================
//==================================================================================================================
$('.swiper', this).each(function(i) {
var nwidth = options.width*i;
$(this).css('left',nwidth);
});
//==================================================================================================================
//==================================================================================================================
//==================================================================================================================
//==================================================================================================================
$('.swipe_slide', this).swipe(function(evt, data) {
if(data.direction=='left'){
//alert('count: '+options.count+" amount: "+options.amount);
if(options.count != options.amount){
moveWidth = options.count * -options.width;
$('.swipe_slide',options.thisone).css( "left" ,moveWidth );
options.count++
}else{
return
}
}else{
if(options.count!=1){
moveWidth = moveWidth+ options.width;
$('.swipe_slide',options.thisone).css( "left" ,moveWidth );
options.count--
}
else{
return
}
}
});
//==================================================================================================================
//==================================================================================================================
});
};
})( jQuery );
$(window).bind('orientationchange', $.fn.jCarousel);
You'll have to refactor your plugin a bit so that the function stores the options that are passed into it, otherwise running the function again on orientation change will reset the options to the defaults.