How can I set different duration for each fotorama/slide? (not transition duration) - fotorama

I'm wondering if it's possible to set different fotorama duration for each slide.
Something like this
<div class="fotorama">
<div data-duration="3000">Content here</div>
<div data-duration="10000">Content here</div>
</div>

I handled it this way, by listening to the fotorama.showend event:
In your base JS script, include this... I haven't tested it on a page with multiple fotoramas, so it may affect all on a page, and you would have to modify the variables to target a specific fotorama.
$(function () {
$('.fotorama')
/* Listen to the "showend" event... the "show" event is for the beginning of a transition, while "showend" is at the end of the transition. */
.on('fotorama:showend',
function (e, fotorama, extra) {
/* do a switch on the active index + 1, if you want the current frame at base 1 instead of base 0 */
switch (fotorama.activeIndex + 1){
case 2:
fotorama.setOptions({autoplay: 3000});
break;
case 5:
fotorama.setOptions({autoplay: 15000});
break;
case 6:
fotorama.setOptions({autoplay: 7000});
break;
case 7:
fotorama.setOptions({autoplay: 20000});
break;
case 8:
fotorama.setOptions({autoplay: 2000});
break;
default:
/* You could choose to always set the autoplay to a default value here as shown, but it may be more efficient to just always set it back to default on the first slide of a "default duration" sequence (above ex. slide 2 of slides 2-4, or slide 8 of slides 8-the end), instead of setting a new autoplay value on each and every slide regardless of whether or not it's needed. */
fotorama.setOptions({autoplay: 2000});
break;
}
/* see the event fire in developer console, for debugging only */
console.log('## ' + e.type);
console.log('active frame', fotorama.activeFrame);
console.log('additional data', extra);
}
)
});
It's important to recognize that the "show" and "showend" events are slide-specific, not slideshow-specific.
This is also handy if you want to change other attributes after the end of certain slides, such as switching from a crossfade to slide transition, or to speed or slow the transition-duration.
If you want to act upon something at the beginning of a transition, listen to "fotorama:show"... full list of events to listen to, with console debugging code from the API page:
$(function () {
$('.fotorama')
// Listen to the events
.on('fotorama:ready ' + // Fotorama is fully ready
'fotorama:show ' + // Start of transition to the new frame
'fotorama:showend ' + // End of the show transition
'fotorama:load ' + // Stage image of some frame is loaded
'fotorama:error ' + // Stage image of some frame is broken
'fotorama:startautoplay ' + // Slideshow is started
'fotorama:stopautoplay ' + // Slideshow is stopped
'fotorama:fullscreenenter ' + // Fotorama is fullscreened
'fotorama:fullscreenexit ' + // Fotorama is unfullscreened
'fotorama:loadvideo ' + // Video iframe is loaded
'fotorama:unloadvideo', // Video iframe is removed
function (e, fotorama, extra) {
console.log('## ' + e.type);
console.log('active frame', fotorama.activeFrame);
console.log('additional data', extra);
}
)
// Initialize fotorama manually
.fotorama();
});

Related

Show textareas values in parent component as a list & not showing tabs on page load

I created a form with Angular Material, inside the form I have a mat-radio-group for radio buttons, and mat-tab-group
For tabs, each one of this tabs have an input. I have two questions.
1 - The tab group doesn’t show when the page loads, I have to click in a radio button to see the tabs, I don’t know why this is happening.
2 - I am getting the values from the textareas and I am showing them in the parent component, When I write a new value in the text area, this one replace the previous value, but I need all the values from the different textareas.
1st issue - not able to see tabs on reload/load
actually, as per the code, we are getting console errors, and because of that, we are not able to see tabs. it is breaking at the radio group itself.
console errors
Fix for the above issue - remove the formControlName="titleAction" for each radio button.
2nd issue - not getting all tabs data in a parent from child comp
so... here you are not sending all tabs data. that is the actual issue.
I did a few changes in child and parent as per your expected result.
sending array from child like below
submit() {
this.updateDataEvent.emit({formdata:this.form.getRawValue(), tabs: this.tabs});
}
and changed code in the parent component while updating a few global variables
updateData(selection: any): void {
this.titleScreening =
selection.formdata.titleAction === 'change'
? selection.formdata.titleText
: 'Original Title';
//Empty Inputs
if (this.titleScreening === '') {
this.titleScreening = 'Original Title';
}
this.divTag = this.divTag2 = ''
selection.tabs.forEach((x) => {
// this.name = x.fullName;
var appendElement = '<li> <span> Name </span>' + x.name + '</li>';
this.divTag = this.divTag + appendElement;
// Show in parent component
var appendElement2 = '<li> Value: ' + x.value + '</li>';
this.divTag2 = this.divTag2 + appendElement2;
});
// selection.fullName === ''
// ? (this.divTag = '')
// : (this.divTag = appendElement);
}
stackblitz

AngularJS- how to return multiple buttons? Return a promise that resolves to an array?

I have an AngularJS application, and on one of the pages, there is a button that opens a dialog allowing the user to configure a particular widget. That widget displays a table, and I have recently added the functionality to allow the user to add a single button to a given table cell. This functionality all currently works correctly.
I am now trying to extend the functionality, to allow a user to add multiple buttons to a single table cell.
The way that the user currently adds a button to a cell, is by typing the address of the page that they want to add the button for into a text field on the dialog- the page that they type must be preceded with a : character. However, that text field is also used to display variable values, so for example, to display a variable value, they would type the name of the variable, i.e. myVariable, but to add a button which would link to the Home page, they would type :pages/home.
The value that they type into the text field is converted to the appropriate object for display in the table by a function called toItemObj(item).
This function is defined with:
function toItemObj() {
...
switch(widgetObj.name) {
case 'table':
...
widgetObj.table = angular.copy($scope.widget.table);
...
var multiBtns = ";";
var btnArray = [];
var btnTmplArray = [];
var pageTitle;
...
angular.forEach(widgetObj.table.rows, function(row) {
if(row.length > 0) {
reducedRows.push(row.map(
function(vrbl, idx) {
...
if(widgetObj.table.header[idx].startsWith(":")) {
if(vrbl.vrbl.includes(multiBtns)) {
btnArray = vrbl.vrbl.split(multiBtns);
if(btnArray[0].startsWith(":")) {
brnArray[0] = btnArray[0].split(":")[1];
}
var btnArrayBtn = 0;
angular.forEach(btnArray, function() {
var btnTmpl = $compile(
'<a href="javascript:;" ng-click="goToPage(target)"' +
'class="btn btn-xs btn-brand">Go to page</a>')
toVrblItemObj(btnArray[btnArrayBtn]);
compiledBtns.push(btnTmpl);
console.log("compiledBtns: ", compiledBtns);
})
} else {
console.log("Vrbl does not include multiBtns: ", vrbl.vrbl);
}
} else {
console.log("Value of widgetObj.table.headers[idx]: ", widgetObj.table.headers[idx]);
}
console.log("Returning vrbl.vrbl: ", vrbl.vrbl);
return vrbl.vrbl;
}
));
}
});
widgetObj.table.rows = reducedRows;
...
break;
In the if(vrbl.vrbl.includes(multiBtns)) { block, I am checking whether the value given to the button by the user contains a ; character, and if it does, I am splitting that string on the ; character, and compiling a button for each of the split elements, then pushing that compiled button to a list called compiledBtns.
The problem I'm having is with returning multiple buttons when they've been compiled...
When I load the page with this code as it is above, and try adding a button to a table cell, if I just add a single button to the cell (i.e. type something like :pages/page1 into the field in the dialog, then that button is displayed correctly. But, if I try to add more than one button to a table cell, (i.e. type something like :pages/page1;pages/page2 into the field in the dialog), then the compiled buttons are not displayed, and I just get the text that I actually typed:
I thought that this might be because my return statement is only returning the single vrbl.vrbl element (i.e. a single button), so I tried changing it to:
if(widgetObj.table.headers[idx].startsWith(":")) {
console.log("column heading starts with : - returning compiledBtns- ", compiledBtns);
return compiledBtns;
} else {
return tag.tag;
}
i.e. if the column is a 'buttons' column (the heading starts with a : character, then it should return the list of compiled buttons, but if not, then it should return a variable.
Doing this caused the table to be displayed as:
i.e. it stops even the cells with only one button from showing the button...
The console is showing me that the buttons are being compiled:
compiledBtns: (2) [ƒ, ƒ]0: ƒ publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn)1: ƒ publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn)length: 2__proto__: Array(0)
ctrl.js:467 Returning tag.tag: :pages/auth;pages/userpage1
but for some reason, they're just not being shown...
Can anyone explain what I'm doing wrong here? How can I get my function to return and display multiple buttons in the same table cell?
Edit
I've just noticed that when only entering one button in a table cell, the button is only displayed/ rendered as a button when you select it from the 'autocomplete' list that's displayed when you start typing- i.e. if you type out the page address in full, and click 'Preview' on the dialog, the button is not displayed in the table- it just shows the textual address that you typed.
When trying to add multiple buttons to the table cell, obviously the autocomplete doesn't recognise any pages with the address of two combined page address (separated with a ;), so you can't select more than one page from the drop down at a time.
How would I enable the selection of multiple items from the autocomplete drop down? The autocomplete functionality is defined in the HTML:
<div class="repeat-item-animation" data-ng-repeat="row in widget.table.rows">
<div class="row">
<div class="col-sm-12 input-addon-btn widget-picker-table-row-input">
<tags-input min-length="1"
um-max-tags-strict
key-property="tag"
data-ng-model="row"
display-property="tag"
template="tagItem.html"
replace-spaces-with-dashes="false"
on-tag-adding="onAddingTagItem($tag)"
on-tag-added="warning.rows = undefined"
um-tags-input-warning="{{warning.rows}}"
max-tags="{{widget.table.headers.length}}"
placeholder="Start typing a tag name or some text">
<auto-complete min-length="1"
load-on-focus="true"
load-on-empty="true"
display-property="tag"
select-first-match="false"
template="autocomplete.html"
source="autocompleteTagsFilter($query)">
</auto-complete>
</tags-input>
<a href data-ng-click="widget.table.rows.splice($index, 1)"
class="btn-icon btn-icon-sm btn-config btn-danger">
<span class="glyphicon glyphicon-minus-sign"></span>
</a>
</div>
</div>
I've Google'd a bit, but can't find any properties for the above <auto-complete> tag that would appear to enable selection of more than one element... Anyone have any suggestions?
Edit
So it seems that the auto-complete feature is actually a part of the implementation of ngTagsInput, which it seems the application is using to provide this functionality. From reading its documentation, it seems that the source parameter is the one I want to be using to return an array of strings (i.e. the links for multiple pages that I want to add buttons for). In the 'Parameters' table under the 'Usage' section of the autoComplete directive in ngTagsInput, the description of source is:
Expression to evaluate upon changing the input content. The input value is available as $query. The result of the expression must be a promise that eventually resolves to an array of strings.
In my application, the source attribute is currently set to the function autocompleteTagsFilter($query), which is defined with:
$scope.autocompleteTagsFilter = function(query) {
// Use last valid query keyword instead if input keyword is undefined
// When the first character of the tag that the user enters is a ':',
// the drop down should display a list of available user pages, not tags.
// Put the 'if(!query && lastTagQryKw)' inside the 'else', so that this
// is run whenever the entered tag name doesn't start with ':'
if (query.startsWith(":")) {
console.log("query starts with ':' ", query);
/*Check whether string has a ';' & split it if it does */
if(query.includes(";")) {
var strings = query.split(";");
console.log("strings[0]: ", strings[0]);
console.log("strings[1]: ", strings[1]);
var strings0 = strings[0].split(":")[1];
// console.log("strings0: ", strings0);
var strings1 = strings[1].split(":")[1];
// console.log("strings1: ", strings1);
/*Now need to pass each string to 'toTagItemObj(), to make it a tag item object', so that it can be displayed as a button */
angular.forEach(strings, function(string, btnString) {
// console.log("angular.forEach - value of string: ", string);
// console.log("angular.forEach - value of btnString: ", btnString);
toTagItemObj(string);
})
}
// Split the 'query' search string on ':', to use only the string
var buttonsQuery = query.substring(1);
if (!buttonsQuery && lastTagQryKw) {
buttonsQuery = lastTagQryKw;
}
/*check whether the buttonsQuery variable contains a ';' - if it does, split it */
if(buttonsQuery.includes(';')) {
console.log("buttonsQuery includes ; - ", buttonsQuery);
var btnsToDisplay = buttonsQuery.split(";");
console.log("btnsToDisplay: ", btnsToDisplay);
}
// Declare variables to be used in 'for' loop
var userPages = pagesPresets;
var page;
var result = [];
// 'For' loop should iterate through the list of user pages,
// and remove path, so that only the page name is shown
for (page in userPages) {
page = userPages[page];
// If the page key starts with 'buttonsQuery', and its length
// is greater than 6 (i.e. just 'pages/' shouldn't be displayed)
// add the page to the list of pages to be displayed.
if (page.key.startsWith(buttonsQuery) && page.key.length > 6) {
result.push(page.key);
}
};
if (result.length > 0) {
lastTagQryKw = query;
}
// Return the list of pages that match what the user has typed
return result;
// Otherwise, if the user types something that does not start with ':',
//then it should be a tag- search for tags that match this term
} else {
if (!query && lastTagQryKw) {
query = lastTagQryKw;
}
var result = Object.keys(fxTag.getTags()).filter(function(name) {
return name.indexOf(query.toLowerCase()) !== -1;
});
if (result.length > 0) {
lastTagQryKw = query;
}
return result;
}
};
As it currently stands, this autocompleteTagsFilter() function is returning an array of strings in its return statement. The array of strings is what's shown in the list of auto-complete options available, but the button is only actually created when selecting one of them.
According to the documentation, I need this to return a promise which resolves to an array of strings, but I'm not sure how I'd do this... Can anyone suggest a way I might make this function return a promise that resolves to an array of strings?
Edit
The template code for the markup/ cell rendering as it currently stands is:
angular.module('app.widget').run(function($templateCache) {
// Add basic suggestion template of autocomplete
$templateCache.put(
'autocomplete.html',
'<div class="pull-left autocomplete-text autocomplete-full-text">' +
'<span ng-bind-html="$highlight($getDisplayText())"></span></div>'
);
$templateCache.put('tagItem.html',
'<div data-ng-click="data.nounit = data.isTag && !data.nounit" ' +
'class="glyphicon-clickable" um-tag-item-disabled>' +
'<span class="tag-item-icon glyphicon glyphicon-tag" ' +
'data-ng-show="data.isTag"></span><span class="tag-item-text">' +
'{{$getDisplayText()}}</span><span class="tag-item-unit" ' +
'data-ng-hide="!data.units || data.nounit">({{data.units}})</span>' +
'<a class="remove-button" ng-click="$removeTag()">' +
String.fromCharCode(215) + '</a></div>'
);
}).controller(...){
I tried changing this to use an ng-if, so that it would show a different template for when buttons should be displayed for multiple pages, but this gives me an error in the console that says:
Error: [ng:areq] Argument 'WidgetPickerCtrl' is not a function, got undefined
I got this error after changing the template to:
angular.module('ultimetric.widget').run(function($templateCache) {
// Add basic suggestion template of autocomplete
$templateCache.put(
'autocomplete.html',
'<div class="pull-left autocomplete-text autocomplete-full-text">' +
'<span ng-bind-html="$highlight($getDisplayText())"></span></div>'
);
$templateCache.put('tagItem.html',
'<ng-if="data.isPages">' +
'<div data-ng-click="data.toPageBtn = data.isPages && !data.toPageBtn" ' +
'class="glyphicon-clickable" um-tag-item-disabled>' +
'<span class="tag-item-icon glyphicon glyphicon-tag" ' +
'data-ng-show="data.isBtn"></span><span class="tag-item-text">' +
'{{$getDisplayText()}}</span><span class="tag-item-text">' +
'data-ng-hide="!data.units || data.toPageBtn">({{data.unnits}})</span>' +
String.fromCharCode(215) + '</a></div>'
'<div data-ng-click="data.nounit = data.isTag && !data.nounit" ' +
'class="glyphicon-clickable" um-tag-item-disabled>' +
'<span class="tag-item-icon glyphicon glyphicon-tag" ' +
'data-ng-show="data.isTag"></span><span class="tag-item-text">' +
'{{$getDisplayText()}}</span><span class="tag-item-unit" ' +
'data-ng-hide="!data.units || data.nounit">({{data.units}})</span>' +
'<a class="remove-button" ng-click="$removeTag()">' +
String.fromCharCode(215) + '</a></div>'
);
}).controller(...){
Any ideas how I can get the autocomplete to show multiple buttons at the same time?

Firefox WebExtensions: How to open a background page as a unique tab?

The question: In Firefox WebExtensions, from arbitrary background origins, how can I open an arbitrary page in a tab, uniquely?
Requirements:
1) Arbitrary background origins. My initial use case is from browser_action contextMenus. However, the technique ought to work from popup, options, or any custom background script. For the moment, not concerned with content pages. Although if the technique works for them too, great.
2) Open an arbitrary page in a tab. My initial use is for the options page, but the technique should work for arbitrary background pages (options, popup, or custom pages).
3) Open uniquely. If it's the first time, open in a new tab. If the tab was previously opened, focus to the existing tab, don't create duplicate tabs.
4) If I close a tab, I need to make sure I remove my reference to the previously opened tab ID.
I've answered my own question with one possible solution.
If the solution could be improved upon, let me know. Thank you.
If the question is a duplicate, I'll remove and post solution elsewhere if appropriate.
This is an attempt to answer my own question, with comments inline.
This simple example assumes all pagers live in the top level directory, but this is arbitrary and easily changed.
Essentially it consists of four parts:
1) A global object tabIDs to hold the page names (without '.html'). You could change this to be full path including extension, or keep the page name as a short name and modify technique to use another option like path for the full path name, etc.
2) An async function (to make use of the new await feature) named tabCreate to determine if one is already open and switch to it or create a new tab.
3) An onRemoved event handler, tabRemove to handle cleanup of tabIDs after a tab is closed (if it was one of interest).
4) A usage example, from a context menu item, passing some a page and a panel option, which have no use in this question, but are part of another question, and just here for demonstration purposes.
background.js:
// tabID contexts
// global var to keep track of different tabs,
// i.e. options.html, popup.html and so on.
var tabIDs = {
'options': null,
'popup': null,
}
// Requires Firefox 52.0+ to use async/await
// opts correspond to contexts above in tabIDs
// of the form { 'page': 'options' } or { 'page': 'popup' }
// note if using Node.js, this may require v7+ and --harmony_async_await
async function tabCreate ( opts ) {
var tab;
if ( tabIDs[ opts.page ] !== null ) {
// should probably bring window to front first... oops
// ..
// switch to tab
tab = await browser.tabs.update( tabIDs[ opts.page ], { active: true } );
} else {
tab = await browser.tabs.create( {
'url': opts.page + '.html'
} );
tabIDs[ opts.page ] = tab.id;
}
console.log( '**** opts.page = ' + opts.page + ', opts.tab = ' + opts.tab + ', tab.id = ' + tab.id );
}
// When tabs are closed, see if the tabID is in tabIDs,
// and if so, set it to null
function tabRemove ( tabID, removeInfo ) {
console.log( 'Closed TAB ' + tabID );
Object.keys( tabIDs ).forEach( function( key, index ) {
if ( tabIDs[ key ] === tabID ) {
tabIDs[ key ] = null;
return;
}
} );
}
browser.tabs.onRemoved.addListener( tabRemove );
/*
* Context Menus
*/
browser.contextMenus.removeAll( );
browser.contextMenus.create( {
title: 'My Web Extension',
contexts: [ 'browser_action' ],
} );
browser.contextMenus.create( {
title: 'Options',
contexts: [ 'browser_action' ],
onclick: myWebExt_Options
} );
function myWebExt_Options ( ) {
tabCreate( {
'page': 'options',
'panel': 1,
} );
}
Another approach might be to add an event listener to each page, and when closed, send a message back to background.js, but that seems much more complicated with little or no benefit.

How to fix lags/clipping on mobile with a dynamic background-position?

I'm working on a small app where the user move the background with his finger on the screen.
The background-position is defined by the position of the finger.
I'm using angular, so I have this kind of tags:
<div class="main-container" ng-touchmove="onTouchmove($event)">
<div class="image" ng-style="{ 'background-position': bgPosition }"></div>
</div>
And in my controller:
$scope.onTouchmove = function (e) {
$scope.bgPosition = e.touches[0].pageX + 'px ' + e.touches[0].pageY + 'px';
};
However, I have significant lags on mobile and the background is "clipping".
The issue seems to be common and people often suggest to use transform over background-position.
But how could I use transform if the position is based on the movements of the user? Globally, how can I avoid these lags?
I think you would want this:
$scope.bgPosition = e.touches[0].pageX + 'px ' + e.touches[0].pageY + 'px';
to be something like
$scope.transform = 'translate(' + e.touches[0].pageX + 'px, ' + e.touches[0].pageY + 'px)';
(and change { 'background-position': bgPosition } to { 'transform': transform} or similar.)
You will also may need to add another html element between .main-container and .image if you don't want to use main-container to crop the image. Something like
.crop-image {
overflow: hidden;
}
or
.crop-image {
clip: rect(/* dimensions offset from top and left. */)
}
(overflow might work for your needs, but if not check out https://developer.mozilla.org/en-US/docs/Web/CSS/clip and https://developer.mozilla.org/en-US/docs/Web/CSS/clip-path).

angular code not working in dynamically generated content

I know I've had this happen before but not in exactly this way and I'm having trouble making the necessary changes.
I have a div that starts out empty and hidden as part of a gallery. When I click an image, it populates the div and shows it. Along with the image/content, there's some navigation elements as well. Thing is, those are also dynamically generated and not working since the angular needs to recompile. Here's the function that populates:
$scope.picturePop = function(picID){
match = $.grep($scope.gallery, function(obj) { return obj.id == picID; });
pic = match[0].image;
title = match[0].title;
desc = match[0].desc;
closediv = "<div id=\"divClose\" class=\"floatRight\" ng-click=\"closeParent();\">Close</div>";
navDiv = "";
if(picID > 1){
prev = picID - 1;
navDiv += "<div id=\"picNav\" ng-click=\"picturePop(" + prev + ")\">Previous</div>";
}
if(picID < $scope.picCount){
next = picID + 1;
navDiv += "<div id=\"picNav\" ng-click=\"picturePop(" + next + ");\">Next</div>";
}
$('#innerPictureDisplay').html(closediv + "<br/><br/><img src=\"images/paintings/" + pic + "\" /><p><b>" + title + "</b><\p><p>" + desc + "</p>"+ navDiv);
$('#outerPictureDisplay').css('display','block');
};
How do I make that code "recompile" so that the "CLOSE" and navigational items work?
UPDATE
So, I changed my approach but I'm clearly still missing something:
my new HTML:
<div id="outerPictureDisplay" ng-show="picID > 0">
<div id="innerPictureDisplay">
<div id="divClose" class="floatRight" ng-click="picID = 0;">Close</div>
<div id="picNav" ng-click="picturePop({{prevID}});" ng-show="picID > 1">Previous</div>
<div id="picNav" ng-click="picturePop({{nextID}});" ng-show="picID < picCount">Next</div>
<img src="images/paintings/{{thisPic.image}}" />
<p>
<b>{{thisPic.title}}</b>
</p>
<p>{{thisPic.desc}}</p>
</div>
</div>
my new function:
$scope.picturePop = function(picID){
match = $.grep($scope.gallery, function(obj) { return obj.id == picID; });
$scope.thisPic = match[0];
$scope.picID = picID;
$scope.nextID = picID + 1;
$scope.prevID = picID - 1;
var $content = $('#innerPictureDisplay');
var scope = $content.scope();
$compile($content.contents())(scope);
};
When I click on a pic, it all loads up fine but when I click on a Previous or Next, it closes since it's registering picID as null. I checked and the numbers are indeed getting printed in the code, but angular doesn't see them on the click. They're coming through as undefined - seemingly the compile issue. I tried implementing it all as a directive, even using the suggested link, but I must've done something off since it didn't help.
You can use the angular service $compile like so:
$compile("<a ng-click='myFunction()'>my html</a>")(scope);
And of course you must inject him wherever you are.
Anyway, for God sake, don't use DOM manipulation this way. You should use a directive and feed the content from a model instead.
UPDATE 1
I sugest use incuna's bindHtmlCompile directive that you can found here.

Resources