CollectionFS images not fully loading - javascript

I've just come across a werid problem, It seems my images are sometimes only loading slightly. Sometimes when I refresh the page manually they load fully but a lot of the time this happens - http://i.gyazo.com/a7050e430c79aa31ba557fc8271f1502.png
Not really sure why this is happening, I'm using collectionFS with cfs-ejson-file and cfs-s3 to store the images.
Here some example code ( main profile image )
Template code -
<div class="profile-avatar" style="height: 261px; margin-bottom: 30px;">
{{#if avatar }}
{{#if avatarReady }}
<img width="261px" height="261px" src="{{avatar.url}}" class="profile-avatar-img thumbnail" alt="Profile Image">
{{else}}
<div class="activity-spinner">
{{>spinner}}
</div>
{{/if}}
{{else}}
<img data-src="holder.js/100%x100%/text:No Profile Photo">
{{/if}}
</div> <!-- /.profile-avatar -->
Js code -
Template.profileLeft.helpers({
avatar: function(){
if(this.profile && this.profile.images)
return Images.findOne(this.profile.images[0]._id);
},
avatarReady: function(){
if(this.profile && this.profile.images){
var image = Images.findOne(this.profile.images[0]._id);
return image && image.isUploaded && image.hasStored("images");
}
},
});

I was seeing a similar problem to what you described. When I would upload an image and immediately display it, initially only a portion of it shows. I'll go into what I found and hopefully it helps.
I found that this was only happening when I would use a custom transformWrite: function in CollectionFS. What I believe is happening is there is a race condition causing the image.isUploaded() and image.hasStored() functions to return true before GraphicsMagick has finished writing the entire file. The partially processed image is then requested and cached by the web server. Like you said, sometimes when you refresh, the image will be then fully loaded.
As an example:
Your avatar URL that shows a partially processed image:
http://yourdomain.com/cfs/files/images/Bun5qheJDeaZDp5Mf/DSC_5498.JPG?&store=images
Add a fake query param to get around the cache, and the full image should be displayed:
http://yourdomain.com/cfs/files/images/Bun5qheJDeaZDp5Mf/DSC_5498.JPG?&store=images&bust=cache
What I ended up doing was setting an additional value on the model that was triggered whenever the image was finished processing. Below is an example of a gm function to process an image. Once the function is finished, another function is called "finishedProcessing" that sets the value on the model.
Sample Code:
gm(readStream, fileObj.name())
.resize(1000).quality(75)
.stream(function(err, stdout, stderr) {
if (err) {
return err;
}
stdout.pipe(writeStream);
// Update value when processing is finished
stdout.on('end', function(){
finishedProcessing(fileObj._id);
});
});
You can then grab the value of image.finishedProcessing before displaying the image:
avatarReady: function(){
if(this.profile && this.profile.images){
var image = Images.findOne(this.profile.images[0]._id);
return image && image.isUploaded() && image.hasStored("images") && image.finishedProcessing;
}
}
This seemed to work pretty well for me. Even if you're not using GraphicsMagick, there may be a similar condition that's happening when the file is being processed or saved.
Also, on a side note, I believe you need to call "image.isUploaded()" instead of "image.isUploaded" in your helper function to get the proper returned value.
UPDATE: On another another side note, I found that after refactoring if you do not have your collection settings set to Allow Update, then only the first chunk of the image will be saved and it can also cause the issue you are seeing.
Images.allow({
update: function(userId, file, fields, modifier) {
// This is to check whether or not CollectionFS is trying
// to update the Image
var validCFSUpdate = _.intersection(fields, ['chunkSize', 'chunkCount', 'chunkSize']).length > 0
// Only allow logged in users and CollectionFS to update the Image
return userId && validCFSUpdate;
}
});

Related

DOM elements manipulation with javascript persists after reload or going to next page in pagination

I am trying to toggle a view between grid and list view mode on my frontend HTML page. I am able to do this fine with dom and HTML classes manipulation by toggling "display: none" between two containers. However, when I go to the next product page(through pagination) or when I reload the page, the default view is the one that appears and not the one that was last toggled. Is there a way to persist the view in case a page reload or product pagination changes? thank you.
here is the dom code that achieves this :
viewList.addEventListener('click', function() {
this.classList.add('view__active');
viewGrid.classList.remove('view__active');
gridItem.classList.add('hidden');
listItem.classList.remove('hidden');
});
viewGrid.addEventListener('click', function() {
this.classList.add('view__active');
viewList.classList.remove('view__active');
gridItem.classList.remove('hidden');
listItem.classList.add('hidden');
});
So far I found that I have to use localStorage to achieve this. but is there a better way to do this?
Essentially what is happening is when you request something from the server, the server responds with an HTML document, and whichever scripts associated with that document is run, So whatever JS executed in the first request is not in context when the second request(paginate or reload) is made.
So you need a way to persist information across these page loads, For that, you have 3 options.
Use sessionStorage.
Use localStorage
Use Cookies.
Of the 3 above the easiest would be to use either option 1 or 2.
Replying to your comment,
Also, If I am using localStorage, What am I using to store the view state?
I'm not quite clear as to what you mean by "What you are using to store the state" If your question is about where your data is stored, you need not worry about it as this is handled by the browser. If your question is about "How" to store it you can go through the MDN docs attached in option 1 or 2. This is simply storing a key-value pair as shown in the docs
localStorage.setItem('preferedView', 'grid'); You can add this to your on click handlers as follows,
viewList.addEventListener('click', function() {
this.classList.add('view__active');
viewGrid.classList.remove('view__active');
gridItem.classList.add('hidden');
listItem.classList.remove('hidden');
localStorage.setItem('preferedView', 'grid');
});
viewGrid.addEventListener('click', function() {
this.classList.add('view__active');
viewList.classList.remove('view__active');
gridItem.classList.remove('hidden');
listItem.classList.add('hidden');
localStorage.setItem('preferedView', 'list');
});
Then when loading a new page at the top of your script you can get the users preferedView(if existing) via const preferedView = localStorage.getItem('preferedView');
Here is a complete example from MDN
In order for anyone to find an answer for a similar task, thanks to #Umendra insight, I was able to solve this by using this :
function viewToggeler(viewBtn1, viewBtn2, view1, view2, viewStord) {
viewBtn2.classList.add('view__active');
viewBtn1.classList.remove('view__active');
view1.classList.add('hidden');
view2.classList.remove('hidden');
sessionStorage.setItem('preferedView', viewStord);
}
viewList.addEventListener('click', () => {
viewToggeler(viewGrid, viewList, gridItem, listItem, 'list');
});
viewGrid.addEventListener('click', () => {
viewToggeler(viewList, viewGrid, listItem, gridItem, 'grid');
});
if (sessionStorage.getItem('preferedView') === 'grid') {
viewToggeler(viewList, viewGrid, listItem, gridItem, 'grid');
} else if (sessionStorage.getItem('preferedView') === 'list') {
viewToggeler(viewGrid, viewList, gridItem, listItem, 'list');
}
I ended up using sessionStorage over localStorage because it empties itself on window/tab closing which might be the most desirable result. localStorage persists even after exiting the browser and opening it back.
Also, at any point someone wants to empty the sessionStorage on exit, I used :
window.addEventListener('onbeforeunload', () => {
sessionStorage.removeItem('preferedView');
});

Why isn't NVDA reading my custom error alert consistently?

I've set up a custom html page template for a client's azure b2c login page and the inline errors are getting read back as-expected (can provide additional details about those upon request if it'd be beneficial), but I'm a bit stumped as to why the page-level errors aren't getting read back as well.
The following are the relevant snippets of html from the template that get rendered during the initial page load:
<!-- B2C-Generated Form Element (all relevant html changes happen within this element) -->
<form id="localAccountForm" action="JavaScript:void(0);" class="localAccount" aria-label="Sign in to Your Account">
<!--B2C-Generated Error Container (prior to error / this html is generated/injected by Azure B2C when the page is loaded) -->
<div class="error pageLevel" aria-hidden="true" role="alert" style="display: none;">
<p class="remove"></p>
</div>
<!-- Custom Error Container (prior to error / this html gets added to the template by jQuery once the window is ready) -->
<div role="alert" style="" class="errorContainer no-error" aria-hidden="false">
<p id="pageError" role="" aria-hidden="true" style="" class="remove"></p>
</div>
After the initial content loads (both the content from Azure B2C, as well as the modifications from jQuery), the following logic gets run to ensure all of the error elements on the page are set up properly (or at least that's the intent) & eliminate some differences that may otherwise cause some problems:
initializeAlerts([
'#pageError',
'label[for="signInName"] + .error > p',
'.password-label + .error > p'
]);
// Below functions are loaded & run from a separate .js file:
function initializeAlerts(errorSelectors) {
errorSelectors.forEach((s) => {
// Store the current error & whether or not inline styles were attempting to hide it
var errorMsg = $(s).html();
var errorStyle = `${$(s).attr('style')} ${$(s).parent().attr('style')}`;
// Ensure the parent element has the role="alert" attribute (rather than the error itself)
$(s).attr('role', '');
$(s).parent().attr('role', 'alert');
// Default both the error element & it's parent to be visible
$(s).removeClass('remove').attr('aria-hidden', 'false').attr('style','');
$(s).parent().addClass('errorContainer').addClass('no-error').removeClass('remove').attr('aria-hidden', 'false').attr('style','');
// If an error message is NOT present, add a class to the parent for styling purposes
if (errorMsg) {
$(s).parent().removeClass('no-error');
}
// If the error was supposed to be hidden dynamically via Azure B2C (i.e. it's a standard error that's prepopulated & simply gets shown/hidden), ensure the error itself is hidden (NOT the parent)
if (errorStyle.indexOf('none') > -1) {
$(s).addClass('remove').attr('aria-hidden', 'true');
$(s).parent().addClass('no-error');
}
// If/when the error gets updated, ensure it gets displayed/read
callbackOnDOMUpdate([s], function() {
var currentError = $(s).html();
if (currentError) {
$(s).removeClass('remove').attr('aria-hidden', 'false').attr('style','');
$(s).parent().removeClass('no-error');
} else {
$(s).addClass('remove').attr('aria-hidden', 'true').attr('style','');
$(s).parent().addClass('no-error');
}
});
});
}
function callbackOnDOMUpdate(selectors, callback) {
selectors.forEach(selector => {
$(function() {
var target = document.querySelector(selector);
var observer = new MutationObserver(function(mutations, observer) {
callback();
});
observer.observe(target, {
subtree: true,
childList : true,
characterData : true
});
});
});
}
After all of that runs, if the user enters an incorrect user/pass combination, an error is returned to the page (via the "B2C-Generated Error Container") which looks something like this (depending on the specific error returned):
<!--B2C-Generated Error Container within form (after receiving error) -->
<div class="error pageLevel" aria-hidden="false" role="alert" style="display: block;">
<p class="remove">Unable to validate the information provided.</p>
</div>
Though, the client wants some verbiage/styling changes made, so rather than showing that message as-is, a "remove" class is added to it (which is associated with a display: none !important css rule) and my custom error container is updated to show something similar to the message below (again, actual message may vary depending on the message returned from b2c):
<!-- Custom Error Container (after receiving error) -->
<div role="alert" style="display: block">
<p id="pageError">
<strong>We're sorry, the information entered does not match what we have on file.</strong>
Please try re-entering the information below or you can recover your login info.
</p>
</div>
Unfortunately, while this DOES appear to get read as expected every once in a while, most of the time, the only message I hear read is "Sign in to Your Account form continue button" (which seems to be a combination of the aria label for the form element the changes are nested within, the form element name itself & the name of the last button the user clicks prior to seeing the page update).
I've tried to ensure that the error itself is nested as a child element within a parent element that:
Is always visible (from both a css & aria perspective)
Has role='alert'
(and simply show/hide the error itself via the addition/removal of the "remove" class)
... but I must be missing something somewhere, so any help anyone can offer would be appreciated.
So... as it turns out, all of the above code was fine as-is (with the exception of the fact that #1 above wasn't entirely true). The issue turned out to be related to the following...
When the user clicks the "sign in" button, I used $('body').removeClass('loaded'); which in turn would cause:
A loading animation to be displayed
The element containing all page content to be set to display: none;
When an error was detected, I would then similarly fire off the following commands (in this order):
$('body').addClass('loaded'); (thus making everything visible again)
the logic to read the system error that was returned & populate the custom error area of the code accordingly
... so, I tried removing all jquery related to the loading animation to see if perhaps the problem was related to that & sure enough, the error was read as expected.
That being said, I believe the real issue at play here was that the error/alert update was getting completed before all visibility-related changes had kicked through from the css side & therefore NVDA wasn't reading the alert because, from NVDA's perspective, the error was still hidden.
... hopefully the documentation of this experience helps someone else down the road!

Update image rotator .xml config file without refresh

I have a 3D model being rendered on my site through an image rotator .xml config file. This feature works but I am attempting to render a completely different .xml in place of the previous file through a JS on change event.
I have done a fair bit of reading in order to solve this issue, although I have not found an answer. I have already tried to make the JQuery script into a function as seen below:
function updateModel(xml_file_path) {
console.log('updating room model...');
console.log('xml_file_path: ' + xml_file_path);
// clear past model
$("#wr360PlayerId").empty();
jQuery('#wr360PlayerId').rotator({
licenseFileURL: 'license.lic',
configFileURL: '/static/360_assets/' + xml_file_path,
graphicsPath: '/static/img/basic',
zIndexLayersOn: false,
responsiveBaseWidth: 600,
responsiveMinHeight: 0,
googleEventTracking: false,
});
console.log('rendering: ' + xml_file_path);
}
// clears the old model then updates the configFileURL to the new model
This was successful in clearing the previous model although when I inspect the new model the images used by the image rotator are not being loaded and nothing is displayed.
wr360 documentation
I've also read through the documentation for wr360 above and found a few different ways of loading the image rotator on my site. I've gone through each and attempted to make it update using similar methods as JQuery but each had their own oddities that were difficult to overcome.
There's not much to code to this as for most of it is created dynamically on page load, but I'll try to provide all code necessary below:
js
function updateModel(xml_file_path) {
console.log('updating room model...');
console.log('xml_file_path: ' + xml_file_path);
// clear past model
$("#wr360PlayerId").empty();
jQuery('#wr360PlayerId').rotator({
licenseFileURL: 'license.lic',
configFileURL: '/static/360_assets/' + xml_file_path,
graphicsPath: '/static/img/basic',
zIndexLayersOn: false,
responsiveBaseWidth: 600,
responsiveMinHeight: 0,
googleEventTracking: false,
});
console.log('rendering: ' + xml_file_path);
}
$(document).ready(function(){
$('#rooms').on('change', function() {
updateModel(room.xml_path);
console.log('model updated');
});
});
// truncated for simplicity
html
<div id="wr360PlayerId" class="wr360_player" style="background-color:#FFFFFF;">
</div>
The xml file path is getting passed correctly (checked by the console.log('xml_file_path: ' + xml_file_path);) it just doesn't render the second rotator.
$('#rooms') is a select field, and room.xml_path is the selected rooms .xml file path. With this being said, ideally, the on change event would show the selected model and if the selection changes again it should render the new model (instead of nothing like it currently does).
Either I am missing something or it is impossible to update a model without refreshing the page, either way, any help is appreciated!
You can actually use,
apiObj.reload(xml_path);
to simply reload the image rotator with a new xml file path.

Fully load content before showing it with Angular 2

In an angular component I'm generating an image from a service http call, which then I want to display on the site. However it's taking longer for the image to generate than it takes the site to load.
Thus I'm forced to refresh a few extra times to see/display the actual image when it finally loads.
How can i make ngOnit wait for everything to be generated and loaded before displaying the page?
this.someService.generateImage().subscribe(x => {
console.log('Image is now in folder')}
I want the page to be displayed after this call.
Any hints for this?
You can do like this:
Markup:
<div *ngIf="!isLoading">
// do not show till loading
</div>
Component:
isLoading = true;
this.someService.generateImage().subscribe((x) => {
console.log('Image is now in folder')
this.isLoading = false;
})
On your ngOnInit use :
ngOnInit() {
this.someService.generateImage().subscribe(x => {
//load page content functions.
console.log('Image is now in folder')
});
}
This is a work around since ngOnInit() on itself doesn't wait for async calls
Why stop the ngOnInit execution instead let it load all the dependency just don't show it,
The hack you can apply is hiding the contents of the complete page by a blocking loader with a loader service and show the content of the page when the image is generated. Something like this.
ngOnInit() {
loaderService.show();
this.someService.generateImage().subscribe(x => {
loaderService.hide();
console.log('Image is now in folder')
}
[Extra dependencies stuff .....]
}
You can use ngAfterViewInit() It get executed after dom loaded fully

Laravel routing behavior with PJAX not as expected

I'm building my first lavavel website from scratch and I've run into a behavioral issue with a few routes.
Here is the relevant code for my routes file:
Route::get('work', 'PageController#work');
Route::get('work/{item}', 'PageController#workitem');
And here are the relevant methods:
public function work() {
return view('pages.work');
}
public function workitem($item) {
$v = 'work.'.$item;
if(view()->exists($v)) {
return view($v);
} else {
return view('errors.noitem');
}
}
And here is the relevant part of my view:
#extends('layout')
#section('content')
...
<div class="workflex">
<a class="workitem" href="/work/test"></a>
<a class="workitem" href="/work/test2"></a>
</div>
<div id="loadContent" class="loadContent">
#yield('insert')
</div>
...
#stop
It is worth mentioning that I intend to load the individual workitem pages with PJAX. I have views that the PJAX loads into the the "insert" section based on the URL:
$(document).pjax('a.workitem', '#loadContent');
The user loads the initial work page at the /work subdirectory, and clicks a button to load /work/item pages with PJAX. As the routes suggest, I also want the user to be able to enter a workitem into the URL and be directed to the work page already loaded with that item. This whole system behaves as intended... until I added the following jquery to work.blade.php:
$(document).ready(function() {
$('#loadContent').load("/work/init", function() {
myFade('#loadContent > *', 1); //ignore this function, it's an animation irrelevant to my problem
});
});
This is here as an attempt to load a initial message inside the PJAX loading div #loadContent to tell the user to select a workitem. However, a side effect of this is that now whenever I browser to a /work/item directly (PJAX still loads the pages correctly) the document triggers this jquery and the message overrides the page content.
I was brainstorming ways to allow the work() method in my controller to trigger something that loads this script or passes just the work/init view into the "insert" section.
What do you think would be the best way to solve this? Your answers are greatly appreciated.
I was able to answer my own question. I forgot about the route optional parameters. I changed/added these things:
Route::get('work/{item?}', 'PageController#work');
and in my controller:
public function work($item = 'init') {
$v = 'work.'.$item;
if(view()->exists($v)) {
return view($v);
} else {
return view('errors.noitem');
}
}
Works perfectly now!

Categories

Resources