Accessing common functions/data from React components - asp.net-mvc

I'm new to React and want to understand the right way to handle the following scenario.
Say, I have three React components which are essentially siblings i.e. not nested within each other.
All three components need to know the screen resolution of the user and this resolution may change. For example, the user may set the browser window to a smaller size. Essentially, the view port is in a dynamic state.
I don't want to put the view port detection logic into each component which would produce repetitive code. How do I create a common function that is responsible for view port detection and pass this information to each component?
In the following code sample, I have three components that work independently of each other but they'll all need view port information. What's the right way to handle this?
<div id="navigationHere"></div>
<div id="doSomethingHere"></div>
<div id="handleSomethingElseHere"></div>
<script src="nav.jsx" type="text/jsx"></script>
<script src="doSomething.jsx" type="text/jsx"></script>
<script src="handleSomethingElse.jsx" type="text/jsx"></script>
By the way, I'm using this inside an ASP.NET MVC application and thinking about using ReactJs.Net to handle initial state and pre-rendering on the server side. So, it's important for me to understand the right way to handle this within the context of an ASP.NET MVC application.

This is a good use case for what are known as a higher order components.
First off we define a parent component that keeps track of the screen resolution.
var ViewportManager = React.createClass({
getInitialState: function() {
return this.measureViewport();
},
componentWillMount: function() {
window.addEventListener('resize', this.onResize);
},
componentWillUnmount: function() {
window.removeEventListener('resize', this.onResize);
},
onResize: function() {
this.setState(this.measureViewport());
},
measureViewport: function() {
return {
width: window.innerWidth,
height: window.innerHeight
};
}
});
Once this component is mounted it will keep track of the size of the screen, updating each time the browser window is resized. What we need to do now is pass these values down to any components rendered inside of this one.
<ViewportManager>
<NavigationHere />
<DoSomethingHere />
<HandleSomethingElseHere />
</Viewport>
We'll pass down viewportHeight and viewportHidth to each of the inner components.
var ViewportManager = React.createClass({
// ...
render: function() {
var viewportWidth = this.state.width;
var viewportHeight = this.state.height;
var children = this.props.children;
var additionalProps = {
viewportWidth: viewportWidth,
viewportHeight: viewportHeight
};
var modifiedChildren = React.Children.map(children, function(child) {
return React.cloneElement(child, additionalProps);
});
return (
<div className='viewport-manager'>
{{ modifiedChildren }}
</div>
);
}
});
Now all of the components directly inside your ViewportManager will receive two additional props.
var NavigationHere = React.createClass({
render: function() {
// I can see this.props.viewportWidth
// and this.props.viewportHeight
}
});

Related

A-Frame + AR.js: How to add objects dynamically in Arjs when tapping on screen?

I'm currently working on an A-Frame AR app by using ar.js (I have to use ar.js). The goal is to dynamically add objects to the scene when tapping on the scene. Unfortunately nothing seems to work out. I managed to change the colors of an existing object when tapping on the screen but not adding a new one. Could someone please help me out here? I would be really grateful!
AFRAME.registerComponent('cursor-listener', {
init: function () {
var currentEl = this.el;
currentEl.addEventListener("click", function (evt) {
console.log("I was clicked!");
currentEl.setAttribute('material', {"color": "blue"});
var el = document.createElement("a-entity");
el.setAttribute('geometry', { "primitive": "sphere", "radius": "1" });
el.setAttribute('position', this.el.object3D.position)
const randomYRotation = Math.random() * 360
el.setAttribute('rotation', `0 ${randomYRotation} 0`)
el.setAttribute('visible', 'true')
el.setAttribute('shadow', {receive: false})
this.el.sceneEl.appendChild(el);
});
},
});
this.el.sceneEl.appendChild(el);
You should append the element to the marker node, and treat it as your "root" element - as it's the one being tracked when detected by camera:
<script>
AFRAME.registerComponent('cursor-listener', {
init:function () {
const marker = document.querySelector("a-marker")
var el = document.createElement("a-sphere");
marker.appendChild(el);
}
})
</script>
<!-- scene and stuff -->
<a-marker preset="hiro>
</a-marker>

Openlayers 3 popover putting each word on one line

Working on an OL3 map, and am getting stuck on popover style. I haven't added any CSS to change it at all and it is currently being handled by default Bootstrap settings.
Okay so the problem: each word is being displayed on it's own line. How can I change this? Or, a better question, how do I control the style of the popovers?
This is was it looks like now: http://i.imgur.com/lrLYbag.png
The code handling popups:
//popups
var element = document.getElementById('popup');
var popup = new ol.Overlay({
element: element,
positioning: 'bottom-center',
stopEvent: false
});
map.addOverlay(popup);
// display popup on hover
map.on('pointermove', function(evt) {
var feature = map.forEachFeatureAtPixel(evt.pixel,
function(feature, layer) {
return feature;
});
if (feature) {
var geometry = feature.getGeometry();
var coord = geometry.getCoordinates();
popup.setPosition(coord);
$(element).popover({
'placement': 'top',
'html': true,
'content': feature.get('name')
});
$(element).popover('show');
} else {
$(element).popover('destroy');
}
});
Thanks!
I think you just need to add some css to the #popup element you're using for the popup.
Something like:
#popup {
min-width: 280px;
}
Take a look at this example http://openlayers.org/en/master/examples/popup.html for styling ideas.

How do I stop my fixed navigation from moving like this when the virtual keyboard opens in Mobile Safari?

I understand that mobile safari has a lot of bugs around fixed elements, but for the most part I've managed to get my layout working correctly until I added a much needed text input to the fixed navigation at the bottom. Now when the user focuses on the text input element and the virtual keyboard appears, my navigation, which is otherwise always fixed at the bottom of the page, jumps up to a really strange spot in the middle of the page.
I'd add some of my code to this post, but I wouldn't be sure where to start. That navigation is fixed at the bottom and positioned to the left and bottom 0, and 100% width. From there, I don't know what's going on, I can only assume it's a mobile safari bug.
It also appears to lose it's position fixed and become relative, only while the text input element is focused on and the virtual keyboard is open.
http://dansajin.com/2012/12/07/fix-position-fixed/ this is one of the solutions proposed. Seems worth a shot.
In short: set fixed elements to position:absolute when any input is focused and reset them when that element is blurred
.header {
position: fixed;
}
.footer {
position: fixed;
}
.fixfixed .header,
.fixfixed .footer {
position: absolute;
}
and
if ('ontouchstart' in window) {
/* cache dom references */
var $body = $('body');
/* bind events */
$(document)
.on('focus', 'input', function() {
$body.addClass('fixfixed');
})
.on('blur', 'input', function() {
$body.removeClass('fixfixed');
});
}
The solutions on the top are some ways to go and fix the problem, but I think adding extra css class or using moderniz we are complicating things.If you want a more simple solution, here is a non-modernizr non-extra-css but pure jquery solution and work on every device and browsers I use this fix on all my projects
if ('ontouchstart' in window) {
$(document).on('focus', 'textarea,input,select', function() {
$('.navbar.navbar-fixed-top').css('position', 'absolute');
}).on('blur', 'textarea,input,select', function() {
$('.navbar.navbar-fixed-top').css('position', '');
});
}
I had a similar problem, but I found a workaround by adding the following css class to the body element on input focus and then removing it again on unfocus:
.u-oh {
overflow: hidden;
height: 100%;
width: 100%;
position: fixed;
}
Taking from what sylowgreen did, the key is to fix the body on entering the input. Thus:
$("#myInput").on("focus", function () {
$("body").css("position", "fixed");
});
$("#myInput").on("blur", function () {
$("body").css("position", "static");
});
Add javascript like this:
$(function() {
var $body;
if ('ontouchstart' in window) {
$body = $("body");
document.addEventListener('focusin', function() {
return $body.addClass("fixfixed");
});
return document.addEventListener('focusout', function() {
$body.removeClass("fixfixed");
return setTimeout(function() {
return $(window).scrollLeft(0);
}, 20);
});
}
});
and add class like this:
.fixfixed header{
position: absolute;
}
you can reference this article: http://dansajin.com/2012/12/07/fix-position-fixed/
I really like the solution above. I packaged it up into a little jQuery plugin so I could:
Set which parent gets the class
Set which elements this applies to (don't forget "textarea" and "select").
Set what the parent class name is
Allow it to be chained
Allow it to be used multiple times
Code example:
$.fn.mobileFix = function (options) {
var $parent = $(this),
$fixedElements = $(options.fixedElements);
$(document)
.on('focus', options.inputElements, function(e) {
$parent.addClass(options.addClass);
})
.on('blur', options.inputElements, function(e) {
$parent.removeClass(options.addClass);
// Fix for some scenarios where you need to start scrolling
setTimeout(function() {
$(document).scrollTop($(document).scrollTop())
}, 1);
});
return this; // Allowing chaining
};
// Only on touch devices
if (Modernizr.touch) {
$("body").mobileFix({ // Pass parent to apply to
inputElements: "input,textarea,select", // Pass activation child elements
addClass: "fixfixed" // Pass class name
});
}
I use this jQuery script:
var focus = 0;
var yourInput = $(".yourInputClass");
yourInput.focusin(function(){
if(!focus) {
yourInput.blur();
$("html, body").scrollTop($(document).height());
focus = 1;
}
if(focus) {
yourInput.focus();
focus = 0;
}
});
Works perfectly for me.
The focusin and focusout events seem to be better suited to this problem than the focus and blur events since the former bubble up to the root element. See this answer on SO.
Personally I use AngularJS, so I implemented it like this:
$window.document.body.addEventListener('focusin', function(event) {
var element = event.target;
var tagName = element.tagName.toLowerCase();
if(!$rootScope.inputOverlay && (tagName === 'input' || tagName === 'textarea' || tagName === 'select')) {
$rootScope.$apply(function() {
$rootScope.inputOverlay = true;
});
}
});
$window.document.body.addEventListener('focusout', function() {
if($rootScope.inputOverlay) {
$rootScope.$apply(function() {
$rootScope.inputOverlay = false;
});
}
});
Note: I am conditionally running this script if this is mobile Safari.
I put an ng-class attribute on my navbar:
<div class="navbar navbar-default navbar-fixed-top" ng-class="{'navbar-absolute': inputOverlay}">
using the following CSS:
.navbar-absolute {
position: absolute !important;
}
You can read more about focusin here and focusout here.
Test this one. It works. I just test it.
$(document).on('focus','input', function() {
setTimeout(function() {
$('#footer1').css('position', 'absolute');
$('#header1').css('position', 'absolute');
}, 0);
});
$(document).on('blur','input', function() {
setTimeout(function() {
$('#footer1').css('position', 'fixed');
$('#header1').css('position', 'fixed');
}, 800);
});
None of these solutions worked for me because my DOM is complicated and I have dynamic infinite scroll pages, so I had to create my own.
Background: I am using a fixed header and an element further down that sticks below it once the user scrolls that far down. This element has a search input field. In addition, I have dynamic pages added during forward and backwards scroll.
Problem: In iOS, anytime the user clicked on the input in the fixed element, the browser would scroll all the way to the top of the page. This not only caused undesired behavior, it also triggered my dynamic page add at the top of the page.
Expected Solution: No scroll in iOS (none at all) when the user clicks on the input in the sticky element.
Solution:
/*Returns a function, that, as long as it continues to be invoked, will not
be triggered. The function will be called after it stops being called for
N milliseconds. If `immediate` is passed, trigger the function on the
leading edge, instead of the trailing.*/
function debounce(func, wait, immediate) {
var timeout;
return function () {
var context = this, args = arguments;
var later = function () {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
function is_iOS() {
var iDevices = [
'iPad Simulator',
'iPhone Simulator',
'iPod Simulator',
'iPad',
'iPhone',
'iPod'
];
while (iDevices.length) {
if (navigator.platform === iDevices.pop()) { return true; }
}
return false;
}
$(document).on("scrollstop", debounce(function () {
//console.log("Stopped scrolling!");
if (is_iOS()) {
var yScrollPos = $(document).scrollTop();
if (yScrollPos > 200) { //200 here to offset my fixed header (50px) and top banner (150px)
$('#searchBarDiv').css('position', 'absolute');
$('#searchBarDiv').css('top', yScrollPos + 50 + 'px'); //50 for fixed header
}
else {
$('#searchBarDiv').css('position', 'inherit');
}
}
},250,true));
$(document).on("scrollstart", debounce(function () {
//console.log("Started scrolling!");
if (is_iOS()) {
var yScrollPos = $(document).scrollTop();
if (yScrollPos > 200) { //200 here to offset my fixed header (50px) and top banner (150px)
$('#searchBarDiv').css('position', 'fixed');
$('#searchBarDiv').css('width', '100%');
$('#searchBarDiv').css('top', '50px'); //50 for fixed header
}
}
},250,true));
Requirements: JQuery mobile is required for the startsroll and stopscroll functions to work.
Debounce is included to smooth out any lag created by the sticky element.
Tested in iOS10.
I wasn't having any luck with the solution proposed by Dan Sajin. Perhaps the bug has changed since he wrote that blog post, but on iOS 7.1, the bug will always surface when the position is changed back to fixed after the input is blurred, even if you delay until the software keyboard is hidden completely. The solution I came to involves waiting for a touchstart event rather than the blur event since the fixed element always snaps back into proper position when the page is scrolled.
if (Modernizr.touch) {
var $el, focused;
$el = $('body');
focused = false;
$(document).on('focus', 'input, textarea, select', function() {
focused = true;
$el.addClass('u-fixedFix');
}).on('touchstart', 'input, textarea, select', function() {
// always execute this function after the `focus` handler:
setTimeout(function() {
if (focused) {
return $el.removeClass('u-fixedFix');
}
}, 1);
});
}
HTH

Google Maps and ASP .NET MVC - Center the map at a marker

I'm building a page with a Google MAP that has a side bar with dynamically created divs linked to positions of markers in the map.
I'm using ASP.NET MVC with JQuery and Google Maps API v3.
Here is a look of it.
This page is loaded in a splash window and is generated dynamically.
In the background page the user types a state or city in an input field and in my controller action I search for all people that are located in that area and return a JSON.
I get the JSON and populate the map with markers and then I make the list of divs in the side bar.
I'd like to add a function to those divs that when they are clicked the map will center at the marker.
I came up with an idea for this solution which I could not finish.
I added class="clickable" and the attributes "Lat" and "Lng" with the values equal to the ones in the markers they are related to, and I tried to get their click event with JQuery and then set the map center with its Lat and Lng like this:
$(".clickable div").click(function(){
$('map_canvas').panTo($(this).attr('lat'), $(this).attr('lng'));
}
I had 2 problems with this approach.
- First, I didn't know how to get the map with JQuery.
I found 2 ways using like $('map_canvas').gMap but it didn't work. Tried a couple more things that I've found here in Stackoverflow but also didn't work.
Second - The JQuery would not catch the event click from the DIVs.
I tested on Google Chrome console the JQuery code and It worked but then my code would not trigger the click.
I tried something like $(".clickable div").click(function(){ alert("test"); } on Google Chrome and it worked, but in my script it did not.
I also tried to add listeners using addDomListener in my code but couldn't get around that either.
Could anyone please give me a light what would be a good way to do this without having to recreate the map when a div is clicked.
I also don't like the idea of adding Lat and Lng attributes to the divs, I don't know if that would work in any browser or if its good practice. I'm just out of solutions to this.
Here is the code I'm using. (I removed some of it to make it shorter and easier to read)
$(document).ready(function () {
//Google Maps API V3 - Prepares the ajaxForm options.
var options = {
beforeSubmit: showRequestPesquisaAdvogados,
success: showResponsePesquisaAdvogados,
type: 'post',
resetForm: false
};
$('#form-pesquisaAdvogado').ajaxForm(options);
//$(".clickable").click(function () { alert($(this).attr('lat')) });
});
function showRequestPesquisaAdvogados(formData, jqForm, options) {
$("#modal-processing-background").show(); //Shows processing splash window
}
function showResponsePesquisaAdvogados(responseText, statusText, xhr, $form) {
$("#modal-processing-background").hide();
//Hide processing window
loadSplashWindow();
CreateMap(responseText);
CreateSideBar(responseText);
}
}
function CreateMap(json) {
var mapOptions = {
center: new google.maps.LatLng(json[0].Endereco.Lat, json[0].Endereco.Lng),
zoom: 5,
mapTypeId: google.maps.MapTypeId.ROADMAP
// marker:true
};
var infoWindow = new google.maps.InfoWindow();
var map = new google.maps.Map(document.getElementById("map-result"), mapOptions);
for (i = 0; i < json.length; i++) {
var data = json[i]
var myLatlng = new google.maps.LatLng(data.Endereco.Lat, data.Endereco.Lng);
var marker = new google.maps.Marker({
position: myLatlng,
map: map,
title: data.Endereco.Logradouro
});
(function (marker, data) {
// Attaching a click event to the current marker
google.maps.event.addListener(marker, "click", function (e) {
// Prepare the infoWindows content.
var contentString = //my content;
infoWindow.setContent(contentString);
infoWindow.open(map, marker);
});
//here I tried to add the listener to the div.
google.maps.event.addDomListener($(".clickable")[i], 'click',
function () {
map.panTo(new google.maps.LatLng($(this).attr('lat'),
$(this).attr('lng')));
});
})(marker, data);
}
}
function CreateSideBar(json) {
$(".sideBarConteiner").empty();
for (i = 0; i < json.length; i++) {
var contentString =
"<div class='clickable' lat='" + data.Endereco.Lat +
"' lng='" + data.Endereco.Lng + "' >" +
//...div's content here
"</div>";
$(".sideBarConteiner").append(contentString);
}
}
If you have any suggestions to make the code better or better practices, since I have only 3 months of experience with programming I might be going in the wrong direction without knowing, so please, feel free to change something if you think it'd be a better way.
I know my post is a bit lenghty, I just wanted to make it clear.
Thank you for your support in advance.
Regards,
Cesar.
I've found a way to do this by creating a global variable in javascript and keeping the map information in order to call it again later.
To to this I just added a var "map" right at the top of the .
<script type="text/javascript">
$.ajaxSetup({ cache: false }); //Forces IE to renew the cash for ajax.
var zoom = 8;
var mapOptions, map;
And then call a method to pan to the right point.
I added the properties Lat and Lng to the div and then pass the div in the javascript function and get the attributes from it.
function CreateSideBar(json) {
$(".sideBarConteiner").empty();
for (i = 0; i < json.length; i++) {
var contentString =
"<div class='clickable' data-lat='" + data.Endereco.Lat +
"' data-lng='" + data.Endereco.Lng + "' onclick='MarkerFocus(this)'>" +
//...div's content here
"</div>";
$(".sideBarConteiner").append(contentString);
}
}
And in my function:
function MarkerFocus(obj) {
var myLatlng =
new google.maps.LatLng($(obj).attr('data-lat'), $(obj).attr('data-lng'));
map.panTo(myLatlng);
}
It worked for me. I hope it helps you too.
Thanks to all for the help and support.
Cesar

How to set nothing on el or tagname to work with jquery ui accordion

Structure of jQueryUI's Accordion is something like this,
<h2>title</h2><div>content</div>
for each item. What I am going to do is create accordion inside of my backbone view through looping, but backbone create div tag for each item so I have html code like this
<div><h2>title</h2><div>content</div></div>
This makes jQuery Accordion control does not work correctly, collapse and expand is not working.
I think this can be solved if I can set nothing on el or tagname, but I cannot find out.
Is there any way to solve this problem?
I think you'd be better off leaving the accordion to one view and then have a separate view inside each panel. After all, the <h2>s are controls for the accordion as-a-whole rather than for a specific panel.
You'd have some per-panel views like this:
var P = Backbone.View.extend({
render: function() {
// Add the panel's content to this.$el (which is a <div> by default).
return this;
}
});
And then an accordion view like this:
var A = Backbone.View.extend({
render: function() {
var panels = [ ... ];
for(var p, i = 0; i < panels.length; ++i) {
p = new P({ ... });
this.$el.append('<h3><a>' + panels[i] + '</a></h3>');
this.$el.append(p.render().el);
}
// The accordion wants to know the sizes of things so
// we let the DOM sort itself out before binding the
// accordion.
var _this = this;
setTimeout(function() { _this.$el.accordion() }, 0);
return this;
}
});
Then you can simply $('#something').append((new A).render().el) and it all works out nicely while leaving everything where it should be.
You could also add a title method to the P views and then A could ask the panel what its name/title/header should be so that all the per-panel information is nicely contained in the per-panel view.
Demo: http://jsfiddle.net/ambiguous/Y49W8/

Resources