MVC Partial View Decoupling Example Code - javascript

In my Current MVC Project, I have one view with so much functionality that is has become unmanageable. In order to resolve this issue, I started added funtionality to partial views and including them in the view but even this was a problem because the underlying javascript (jquery) was so interrelated and intertwinded that it was still a mess. The JS in one partial view would call a refresh method in another partial view and created a tighly coupled disaster.
I decided I needed a way of decoupling the parital views from each other and from the container view.
Here are a few of my objects
Decouple the JS in my parital views so that one view does not need to know anything about another view or the container view.
Namespace the JS code for each view so that I do not run into nameing conflicts between views.
Create the pattern to maintian state on a page refresh (or link to this page)
Put JS into sperate files that still support razor for URL resolution and other functions with one JS file per view or partial view.
Use nameing conventions that allow someone who looks at the container view event wire up and initialization code to understand what is going on and where the refernces refer to.
Be able to pass parameter data from the trigger event to the listeners.
After doing some research and not finding to much information on this, I created a small demo that shows my first attampt at solving this issue. I am hoping that others can see what I am doing and provide even better ways of accomplishing this. In the demo, there are two partial views that communicate with each other without knowing anything about reach other. The contianing view is used to wire up the event handlers and call the functions when an event is raised.
Here is the demo code
The controler does nothing but returns the views and partial views.
PartialViewDecoupleController.cs
namespace EngA.SandboxApplication.Controllers
{
public class PartialViewDecoupleController : Controller
{
// GET: PartialViewDecouple
public ActionResult Index()
{
return View();
}
public PartialViewResult IndexJs()
{
return PartialView();
}
public PartialViewResult MenuPartialView()
{
return PartialView();
}
public PartialViewResult MenuPartialViewJs()
{
return PartialView();
}
public PartialViewResult DisplayPartialView()
{
return PartialView();
}
public PartialViewResult DisplayPartialViewJs()
{
return PartialView();
}
}
}
The index view simply loads all the views
Index.cshtml
#Html.Hidden("id")
#Html.Action("IndexJs", "PartialViewDecouple")
#Html.Action("MenuPartialView","PartialViewDecouple")
#Html.Action("MenuPartialViewJs", "PartialViewDecouple")
<br/>
<br/>
#Html.Action("DisplayPartialView", "PartialViewDecouple")
#Html.Action("DisplayPartialViewJs", "PartialViewDecouple")
The IndesJs file wires up the event handlers and marries the partail views together.
IndexJs.cshtml
<script language="javascript">
$(document).ready(function () {
//Set up Event Handlers
$(document).bind("MenuPartialView_onClick", function (e, p1) {
$("#id").val(p1);
displayPartialView.setup(p1);
});
$(document).bind("DisplayPartialView_onClick", function (e, message) {
menuPartialView.message(message);
});
//Initialize Partial Views on refresh
var id = $("#id").val();
if (id!=null) displayPartialView.setup(id);
});
</script>
Simply a container to recieve data and an button to raise an event.
DisplayPartialView.cshtml
DisplayParitalView<br/>
<div id="display">default</div>
<input type="button" name="ButtonSayHi" id="ButtonSayHi" value="Say Hi" onclick="displayPartialView.onButtonSayHiClick(this)" />
Below, "setup" displays a parameter that is passed to it.
"onButtonSayHiClick" Raises and event
DisplayPartialViewJs.cshtml
<script language="javascript">
var displayPartialView = {
setup: function (id) {
$("#display").html(id);
},
onButtonSayHiClick: function (e) {
var name = e.id;
$(document).trigger("DisplayPartialView_onClick", [name]);
}\
};
</script>
Below, Two buttens to raise events.
MenuPartialView.cshtml
MenuParitalView
<br/>
<input type="button" name="ButtonOne" value="One" onclick="menuPartialView.onButtonOneClick(this)" />
<input type="button" name="ButtonTwo" value="Two" onclick="menuPartialView.onButtonTwoClick(this)" />
Below, Triggers raise events and message displays a message that is sent to it.
MenuPartialViewJs.cshtml
<script language="javascript">
var menuPartialView = {
onButtonOneClick: function () {
$(document).trigger("MenuPartialView_onClick", [1]);
},
onButtonTwoClick: function () {
$(document).trigger("MenuPartialView_onClick", [2]);
},
message: function (message) {
alert("Message: " + message);
}
};
</script>
please let me know if you see better ways of accomplishing the same thing
Thank you
Earl

Firstly, adding scripts directly into the HTML is not a very good idea - put them in separate js files and bundle/minify then. This will save on page load times and make it easier to maintain.
A good approach is to separate your HTML by 'views' - Basically, wrap each of your views in a div with a ID e.g.
<div id="my-app-view-1">VIEW HTML</div>
By doing this, you can then scope your java script like this:
my-app-view-1.js:
(function(global) {
'use strict';
var document = global.document,
$ = global.$;
$(function() {
var $view = $(document).find('#my-app-view-1');
// do processing here
});
}(this));
This will ensure that you have modular js, and also that other scripts wont interfere with this view.

Related

Shared JavaScript File with Different Definitions of a Function Call

I have a 300 line javascript file that sets up jQuery event handlers and other needed functions for a partial view that's used by multiple views within a ASP.NET MVC application. The event handlers handle 99% of everything identically regardless of which view is using the partial. This question is about that 1% difference.
Since JavaScript doesn't have interfaces is it safe to define a function to be called by one or more of the event handlers that processes the things that are different in a separate file that is loaded depending on which view is used? If not, what would be the best way to handle this situation? In other languages I'd use interfaces and/or abstract classes in this situation.
Example:
shared file
$(document).ready(function() {
//shared variables here for methods
$(document).on('click', '.selectable-table tbody tr', function() {
//do shared actions
mySpecificFunction();
//finish shared actions (if necessary)
});
});
Definition1.js
function mySpecificFunction() {
//do stuff
}
Definition2.js
function mySpecificFunction() {
//do other stuff
}
The views would load the appropriate scripts as such:
<script src="definitionX.js"></script>
<script src="sharedScript.js"></script>
The "signature" (term being used generously because javascript) of mySpecificFunction() would be the same for each definition, but something in my gut is telling me that this is bad practice. Is there a better/correct way to do this or a design pattern for this purpose?
I think you can use OOP approach here and you don't need the abstract classes or interfaces for that, instead you can use objects (which are more flexible than in other languages).
For example, you can have a base View prototype with shared code and then load specific view1.js, view2.js where the base prototype will be extended with specific code:
$(document).ready(function() {
// view is a view instance coming from the specific view.js
view.init();
});
// sharedScript.js, view prototype
var View = {
init: function() {
$(document).on('click', '.selectable-table tbody tr', function() {
// do shared actions
// ...
// do specific actions
this.mySpecificFunction();
});
},
mySpecificFunction: function() {
//do specific things, can be left empty in the "prototype" object
return;
}
};
// view1.js
var view = Object.create(View);
view.mySpecificFunction = function() {
alert('view 1');
}
// view2.js
var view = Object.create(View);
view.mySpecificFunction = function() {
alert('view 2');
}
And the views would load shared and specific scripts:
<script src="sharedScript.js"></script>
<script src="view1.js"></script>
This is just a rough idea which can be improved, for example, you may want to concatenate and compress all your js code into the single file for production. In this case the global view variable coming from view1.js, view2.js, etc would become a problem.
An improvement can be some kind of "router" which will detect what view should be instantiated:
$(document).ready(function() {
router.when('/', function() {
view = HomePageView();
}).when('/about', function() {
view = AboutPageView();
});
view.init();
});
The approach outlined above will work but it's not the best approach in terms of maintainability. Adding one file or another via a script tag to import the specific function
doesn't necessarily make it clear to another developer that you have actually changed the behaviour of the event handlers in the shared code.
A simple alternative could be that within each view you would wrap the partial view within a containing element that has an identifying css class to differentiate between the behaviour required at that point.
Then assign event handlers individually for those different css classes:
$(document).ready(function() {
//shared variables here for methods
$(document).on('click', 'div.type1 .selectable-table tbody tr', function() {
//do shared actions
mySharedActions();
mySpecificFunction1();
//finish shared actions (if necessary)
});
$(document).on('click', 'div.type2 .selectable-table tbody tr', function() {
//do shared actions
mySharedActions()
mySpecificFunction2();
//finish shared actions (if necessary)
});
});
This would allow you to keep all your specific functions together in one place and makes the changing behaviour predicated by the css class explicit
for future developers to see.

Order Levels on Underscore Templates, making sure data form Backbone Collection loads in set order?

I have an Underscore.JS template loading in basic text data from my database. These load into two div tags. But I think (I am not sure here, please correct me if I am wrong), that depending on what table Backbone gets first depends on the order of which these div tags are displayed in.
So (a), I have come up with a answer, I just wanted to know weather or not this was the best way of doing it by 'chaining' (if that is the right way of calling it) the success calls, within the same Backbone View, this loads both divs into the same Underscore template. So this is my code currently,
var TextEditView = Backbone.View.extend({
inititalize: function() {
this.listenTo(this.BasicTextModel, "change", this.render);
},
el: $(".BasicTextTemplate"), //Template loader placeholder
render: function() {
var that = this;
var AboutText = new TextAboutCollection(); //Get About page text
var HomeText = new TextHomeCollection(); //Get Home page text
HomeText.fetch({
success: function(Text) {
var GetHomeTxt = _.template( $('script.BasicText').html(), { Text: Text.toJSON() } ); //Load About text into template
that.$el.append(GetHomeTxt);
that.trigger('ChangeTxt', that);
AboutText.fetch({
success: function(Text) {
var GetAboutTxt = _.template( $('script.BasicText').html(), { Text: Text.toJSON() } ); //Load About text into template
that.$el.append(GetAboutTxt);
that.trigger('ChangeTxt', that);
} //End of Success Call to AboutText
}); //End of .fetch for AboutText
}}); //End of Success & .fetch calls for HomeText
} //End of render view function
}); //End of TextEditView
The AboutText is only loaded after the success call for hometext fetch is done, right? Even if I do not get this 100%, it does seem to work for now.
And (b), is there a simple way of adding a 'level' or ID to each div tag making sure Underscore loads each tag in the order I want? Or do I have this completely wrong and should load both these Collections into their own view pointing to there own template? If so I do not understand the point of the template? The way I have them now, I only have the one template that I have re-used!
Please if I am wrong, please correct me
Thanks,
EDIT of the EDIT:
model = new Model({_id:id})
var fetched = model.fetch();
// wait for the model to be fetched
$.when(fetched).then(function(){
view = new View({model:model})
app.content.show(view)
});
from https://stackoverflow.com/a/13601074/2535516
EDIT :
This refers about the comment.
About building code, i think u refer as organization of a backbone project (correct me if i'm wrong). If so, the way i am doing is write my code by module. A module is basically : a BackboneModel/Collection, View, and underscore template.
Architecture :
/
main.js
About/
-- about.html <-- contains the underscore template
-- aboutView.js
-- aboutModel.js
-- aboutCollection.js
Menu/
-- menu.html
-- menuView.js
-- menuModel.js
-- menuCollection.js
And i load it through an AMD lib, mostly require.js
This architecture is a personal, what you will mostly see is :
/
main.js
View/
-- about.js
-- menu.js
Model/
-- aboutModel.js
-- menuModel.js
Collection/
-- aboutCollection.js
-- menuCollection.js
A good example is the TODOMvc architecture
Your code will work, but this is not very readable and will go worse if you'll have to add some fetch :
HomeText.fetch({
AboutText.fetch({
Module1Text.fetch({
Module2Text.fetch({
});
});
});
});
This is call the pyramid of doom.
To avoid this, use promises.
As a note, jquery do promises

Conditionally invoking JavaScript from a bean

How do I conditionally invoke JavaScript from a bean in JSF 2?
I have a class that uses PrimeFaces which uploads user files to a particular folder, but if the user attempts to upload a file with the same name as one that is already present, I want JavaScript in this case to open up a box on the screen asking the user if he wants to overwrite the old file, or cancel the operation. If no file with that name is present, then JavaScript is not called.
I know how to test that a folder has a particular file in it, it is just that I need to know how to invoke JavaScript conditionally.
I would most appreciate any advice on this.
I have looked at variopus resources online, but still cannot get the application to work correctly. basically, this is what I have done, in an included xhtml page I have the following code for the file upload:
<p:fileUpload id="fileUpload" fileUploadListener="#{filters.upload}"
allowTypes="#{filters.uploadTypes}" invalidFileMessage="#{filters.uploadBadType}"
sizeLimit="#{filters.uploadSize}" invalidSizeMessag="#{filters.uploadBadSize}"
update="fileUpload fileTable uploadMessage" description="Select Text File"
disabled="#{filters.disableFileUploadButton}"/>
<!--- Then further in the same file is this: -->
<p:remoteCommand name="remoteCommandOverwrite" actionListender="#{filters.execOverwrite}"/>
The parent xhtml page that includes the above I have the foolowing JavaScript:
<script type="text/javascript">
function popupConfirm() {
var overwrite = confirm('Warning: This will overwrite the existing file - Do you confirm this?');
if (overwrite) remoteCommandOverwrite([{name: overwrite, value: true}]);
}
</script>
In my bean I have the following code in three methods:
public void upload(FileUploadEvent event) {
FacesMessage msg = new FacesMessage("Success! ", event.getFile().getFileName() + " is uploaded.");
FacesContext.getCurrentInstance().addMessage(null, msg);
overwrite = false;
// Do what you want with the file
try {
copyFile(event.getFile().getFileName(), event.getFile().getInputstream());
} catch (IOException e) {
e.printStackTrace();
}
}
public void copyFile(String fileName, InputStream in) {
// Initialization etc.
File file = new File(uploadFull + fileName);
if (file.exists()) {
RequestContext.getCurrentInstance().execute("popupConfirm()");
// Then test to see if overwrite is true or false, and act accordingly
}
// Then I am supposed to get the value of overwrite here:
public void execOverwrite() {
System.out.println("### execOverwrite() ###");
FacesContext context = FacesContext.getCurrentInstance();
Map<String, String> map = context.getExternalContext().getRequestParameterMap();
String soverwrite = (String) map.get("overwrite");
if (soverwrite.equals("true")) {
overwrite = true;
System.out.println("overwrite: true");
}
}
What I am trying to do is first to invoke conditionally the JavaScript function popupConfirm(). On clicking the "Upload" button that is invoked if the codition is true, which is what I want. This is then supposed to call
That works and brings up the confirm() box, but the is never called, so the method execOverwrite() in my bean is also never called, and I cannot pick up the return value and pass it to the code inside the method copyFile(). What is going wrong?
I put this problem on the back burner for about a week, and have just got back to it. I got it to work, and can pass a value back to the bean, but somehow I need to resume execution from the place where JavaScript is called.
To sumarize, my JavaScript contains the following code:
<script type="text/javascript">
function popupConfirm() {
var overwrite = confirm('Warning: This will overwrite the existing file - Do you confirm this?');
if (overwrite) remoteCommandOverwrite([{name: 'overwrite', value: 'true'}]);
}
</script>
And in the xhtml code I have:
<p:fileUpload id="fileUpload" fileUploadListener="#{filters.upload}" ...../>
<!-- Other code -->
<p:remoteCommand name="remoteCommandOverwrite" actionListener="#{filters.execOverwrite}"/>
Then on clicking the file upload button after clicking the choose file button, the code in the JavaScript, as listed above, is executed:
RequestContext.getCurrentInstance().execute("popupConfirm()");
Then on clicking "OK" in the dialog box, this method in the same bean is called:
public void execOverwrite() {
FacesContext context = FacesContext.getCurrentInstance();
Map<String, String> map = context.getExternalContext().getRequestParameterMap();
String soverwrite = map.get("overwrite");
if (soverwrite.equals("true")) {
overwrite = true; }
}
}
where the flag "overwrite" will eventually be tested to see if it is true.
Using various print statements I check that this works. However, the code does not resume executing after encountering the statement: RequestContext.getCurrentInstance().execute("popupConfirm()"); regardless of whether I enter "OK" or "Cancel" in the dialog, which is what i want it to do. It looks as if a callback of some type is required, and would most appreciate some ideas.
According to your tag, you are using PrimeFaces, so there is an easy way to invoke a javascript function from a server side event managed bean method when the browser has completed processing the server response. PrimeFaces gives you a utility class called RequestContext.
public void doActionListenerMethod() {
if (!isValid()) {
RequestContext.getCurrentInstance().execute("MyJSObject.doClientSideStuff()");
}
}
The following will execute the string argument as a Javascript when JSF has finished rendering on the client.

getting requestattributes in javascript

I want to retrieve the request scope attribute in javascript as follows. How can I achieve this?
function caseChanges(req) {
var innervalue= "${dashboardTicketSummary}"
alert("nand you are 1234");
alert(innervalue);
}
Assumin the javascript is included inline on the jsp page -- you're on the right track, you just need to make sure the attribute is set (typically by your controller class) on the HttpServletRequest object, ie:
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse res)
{
DashboardTicketSummary dts = ...; // code to obtain ticket summary here
req.setAttribute("dashboardTicketSummary", dts);
// code to dispatch to your jsp page here
}
Of course there are many other more sophisticated way -- especially is you use popular MVC framework such as Spring
set value to request attributes and write this piece of code in a script tag on jsp page (like index.jsp):
<script>
function caseChanges(req){
var innervalue= "${dashboardTicketSummary}"
alert("nand you are 1234");
alert(innervalue);
}
</script>
for better maintainability, you can gathering all attributes to a global object on jsp, and refer to it on other js files:
index.jsp
<script>
var REQUEST_ATTRS = {
dashboardTicketSummary : '${dashboardTicketSummary}',
otherAttributes :'${otherAttributes}'
};
</script>
other.js
function caseChanges(req){
var innervalue= REQUEST_ATTRS.dashboardTicketSummary ;
alert("nand you are 1234");
alert(innervalue);
}

updating javascript in the body tag using zend framework

Hi not sure if this is possible or not but I want to programaticaly update the <body> tags to change the onload function in my zend framework application.
The App is using layouts so the body tag currently looks like this <body class="trandra">
However in one of my views I have a map from google being loaded and it needs the following in the body tag this particular view <body onload="initialize()" onunload="GUnload()">
As you can understand I don't want this to be hardcoded in my layout as this will cause all matter of nightmares with the different views.
How can this be done programaticaly, if at all it is possible? Im using the headScript functions to add the javascript so is there an equivalant for the body tag?
Thanks in advance...
Approach one - Use a layout variable
One idea would be the following:
<body class="trandra" <?php echo $this->layout()->bodyScripts ?>>
And in your view:
<?php
$this->layout->bodyScripts =
'onload="initialize()" onunload="GUnload()"';
Approach two - Additional JS-file that adds event handlers
Another approach, which is less obtrusive and doesn't affect the HTML whatsoever is to add an additional JS-file in the view that requires the onload- and onunload-handlers. It could look something like this:
<?php
$this->headScript()->appendScript(
'/path/to/javascripts/loadGMaps.js');
In your loadGMaps.js (using prototype)
Event.observe(window, 'load', function onLoadHandler() {
// Code for initializing Google maps here
});
Event.observe(window, 'unload', function onUnloadHandler() {
// Code for unloading Google maps here
});
Instead of putting your Javascript directly in the code, you could also use an non-obstrusive approch : plugging in the javascript when the page is fully loaded.
Have a look, for instance, at a function called addOnLoadEvent (can be found on many websites ^^ )
If you are using a JS Framework, it certainly has that kind of feature :
for jQuery : http://docs.jquery.com/Events/ready#fn
for prototype : http://www.prototypejs.org/api/event/observe
If you register the "plugging-in" with headScript, there should be no need to modify the tag directly.
Developed something like this recently, I've blogged about it here: http://www.evilprofessor.co.uk/311-zend-framework-body-tag-view-helper/
Demo on site and code is available via github.
I'm no expert on the Zend framework, so I don't know if there is any build in functions for this, but you could do something like this:
In layout-file:
body_params?>>
And then in your controller, you set or add to the body_params:
$this->view->body_params='onload="initialize()" onunload="GUnload()"';
I know this is an old thread, but I was looking through some of the suggested solutions and came up with one of my own playing off of some of the ideas I had seen. What I did was I extended Zend_View in my own library files (I'm using a vanilla MVC layout but similar things can be done using a bootstrap.php rather than the Bootstrap class described below)
class Custom_View extends Zend_View
{
protected $bodyAttrs = array();
public function _setBodyAttr($attrName,$attrValue=null) {
$attrName = strtolower(strval($attrName));
if(!(in_array($attrName, HTML::getValidBodyAttrs()))) {
throw new Zend_Exception(__METHOD__." attrName '$attrName' is not a valid BODY attribute!");
}
$this->bodyAttrs[$attrName] = strval($attrValue);
}
public function _getBodyAttrsAsString() {
$bodyAttrs = "";
if(count($this->bodyAttrs) > 0) {
$attrs = array();
foreach($this->bodyAttrs as $_k => $_v) {
array_push($attrs,sprintf("%s=\"%s\"", $_k, $_v));
}
$bodyAttrs = " " . implode(" ", $tags);
}
return $bodyAttrs;
}
}
// some useful tag definitions for HTML
class HTML
{
// HTML attributes as described by W3C
public static $BODY_ATTRIBUTES = array('alink','background','bgcolor','link','text','vlink');
public static $GLOBAL_ATTRIBUTES = array('accesskey','class','contenteditable','contextmenu','dir','draggable','dropzone','hidden','id','lang','spellcheck','style','tabindex','title');
public static $WINDOW_EVENT_ATTRIBUTES = array('onafterprint','onbeforeprint','onbeforeunload','onerror','onhaschange','onload','onmessage','onoffline','ononline','onpagehide','onpageshow','onpopstate','onredo','onresize','onstorage','onundo','onunload');
public static $MOUSE_EVENT_ATTRIBUTES = array('onclick','ondblclick','ondrag','ondragend','ondragenter','ondragleave','ondragover','ondragstart','ondrop','onmousedown','onmousemove','onmouseout','onmouseover','onmouseup','onmousewheel','onscroll');
public static $KEYBOARD_EVENT_ATTRIBUTES = array('onkeydown','onkeypress','onkeyup');
public static $FORM_EVENT_ATTRIBUTES = array('onblur','onchange','oncontextmenu','onfocus','onformchange','onforminput','oninput','oninvalid','onreset','onselect','onsubmit');
public static $MEDIA_EVENT_ATTRIBUTES = array('onabort','oncanplay','oncanplaythrough','ondurationchange','onemptied','onended','onerror','onloadeddata','onloadedmetadata','onloadstart','onpause','onplay','onplaying','onprogress','onratechange','onreadystatechange','onseeked','onseeking','onstalled','onsuspend','ontimeupdate','onvolumechange','onwaiting');
public static function getValidBodyAttrs() {
return array_merge(self::$BODY_ATTRIBUTES,self::$GLOBAL_ATTRIBUTES,self::$WINDOW_EVENT_ATTRIBUTES,self::$MOUSE_EVENT_ATTRIBUTES,self::$KEYBOARD_EVENT_ATTRIBUTES);
}
}
after creating this file I added a method _initView to the Bootstrap.php file pointed to by the index.php and application.ini at the root of the application directory:
protected function _initView()
{
// Custom_View extends Zend_View
$view = new Custom_View();
// Add it to the ViewRenderer
$viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper( 'ViewRenderer' );
$viewRenderer->setView($view);
return $view;
}
The new, extended Zend_View now allows adding your body tags along with some simple checking for validity. Modify your layout's body tag to get the attributes:
<body<?= $this->_getBodyAttrs(); ?>>
Once you have this set up you can add your body tags to any given view
in the controller with
$this->view->_setBodyAttr('key','val');
or in the view with
$this->_setBodyAttr('key','val');

Categories