Stimulus.js way to load a partial? - javascript

We are investigating Stimulus.js for use with our Rails6 app and keep hitting conceptual walls in moving our thinking from jQuery to Stimulus.
For example:
On one part of the page we have a button and when that button is clicked we want to load content into a div on another part of the page. In jQuery this is simple - respond to click event, load in the part from the rails backend into that div.
In Stimulus, how to do this? It looks like everything needs to be in one big controller so that the button can see the 'target div'. So essentially we are writing 'page' controllers, which seems a lot of overhead. Also, it messes up the way we are breaking down the page into partials because now those partials need to share a Stimulus controller.

You definitely don't need to be writing 'page' controllers. The idea is that you write smaller controllers for interactive elements. If you post more about your use case perhaps we can help you split apart your controllers. You are correct though that the controller's element must contain any element registered as a target. Just remember you can access elements outside that context (e.g. in a completely different part of the page) using regular Javascript (in other words document.getElementById can give you access just as easily as using Stimulus targets)
To answer your question though, you can do something like this:
show() {
fetch("/path/to/partial")
.then((res) => res.text())
.then((html) => {
const fragment = document
.createRange()
.createContextualFragment(html);
this.element.appendChild(fragment);
// OR document.getElementById("testy_element").appendChild(fragment)
});
}
What this does is fetch an HTML response from the Rails server and insert it inside of the main controller's element (this.element). For example:
<div class="container" data-controller="test">
<button data-action="test#show">Do the Thing</button>
<!-- partial would appear here -->
</div>

What you are looking for is Stimulus’s sibling library Turbo, made to handle HTML navigation through turbo-frames and partially updating the page through turbo-streams.

Related

Including a custom js file on rails_admin dashboard

I am trying to include a custom js for a custom field that depends on a google maps control, I don't want to mix html and js on a partial file, however, up to now, it appears to be the way to go.
I have checked on the wiki and the only reference about including a custom js is here but it doesn't work.
I only want to be able to organize my javascripts as usual (at assets/javascripts/) and be able to interact with my rails_admin form views. Anyone has any idea on how this should be handled?
To restate: It sounds like you are trying to keep javascript code out of view (html/erb) files.
I can recommend one way you may wish to try.
If you look at the assets/javascript directory you can see the generated javascript files that are created per controller when you are scaffolding a resource.
These files are great for keeping all the code related to the controller context in. there is another file named application.js which is great to keep global javascript routines in.
If you put tags/fields on the elements which you wish to select to bind a javascript method to you are able to keep the methods focused on finding and binding fields sharing the tag.
example:
field you wish to interact with:
Blah
your_controller_name.js:
using jquery you should be able to select the span by the data tag ( you could stored extra infor. you can then also bind methods to the span.
$("span[data-interesting='hi there']").click(function() { console.log('someone clicked the span'); })
You could use a selector that is more general and do something useful to all the matching items.
Good luck!
James.

other than ng-view page loading

I am using a master page having navigation and ng-view on it, partial pages loading successfully under ng-view and with navigation of master page , but I require to load some isolated page like login.html page but not under ng-view and without master page content during route.
I am new on angular and not sure how to do this, login page loaded every time under ng-view.
Please suggest me any way to do this.
Thanks in advanced.
ng-include is your friend. see doc
here is simple way, but you should go to doc and see that there are extra options as to what can be done onload and if you want to autoscroll or do some kind of animation.
<div ng-include="'somefile.html'">
</div>
The one extra note is that ng-include is given an expression so if you have a static reference you need the extra quotes.
One of the great things about Angular is it is very flexible. Ultimately it will depend on your app and how you work. I'm not exactly certain what you are trying to accomplish but, it sounds like a job for the ngInclude directive as Dan mentioned.
You mention a login.html that excludes navigational and other main page content. Using a modal may be a viable alternative interface decision.
If you have everything built and you only want to hide part of the DOM and be done with it then, nghide or ngShow may be the quickest solution.
If there is a lot of stuff on your index.html you may want to separate those things into partials other than the ones connected directly to your routes, and use ng-include="'path/to/partial.html'" to include them. (The "''" are intentional as it likes a string.) Read the docs and experiment with the plunkr. You can do a lot of cool things with ngInclude, especially if you pay attention to its context.
If you combine the ngInclude directive with ng-switch-when, you could put together something like:
<div ng-switch="routeAction">
<div ng-switch-when="extpage" ng-include="'extraneous.content.html'"></div>
<div ng-switch-when="login" ng-include="'login.partial.html'"></div>
<div ng-switch-default ng-include="'default.tpl.html'"></div>
</div>
Where routeAction is tied to your controler which, is tied to your $route or $location.. depending on how you have things set up.

Is it good to generate Html page elements using AJAX queries?

depending upon UI inputs, i need to dynamically change (create or hide) other UI elements.
Also i don't want to refresh my page.Is it good to generate Html page elements using AJAX queries? Also what is the best way to do it?
I am using Knockoutjs's html binding to do the work. Here is the idea:
Create a global viewModel:
var _viewModel = {
body: ko.observable()
}
In the html page:
<div id="dynamic-part" data-bind="html: body" ></div>
In the javascript
ko.applyBindings(_viewModel, $('#dynamic-part')[0]);
whenever you want to load the dynamic-part, you can have kind of javascript code like follows:
$.get('/some/new/page/part', function(data) {_viewModel.html(data);});
When you apply the above technology along with sammy and LAB, you will get a very powerful one-page web application, where all the pages of the application can be load with ajax call.
Normally the way it's done is that the initial page has tags with identifiers, but no content in the divs. As your AJAX results come back, you set the innerHTML of those divs to the retrieved content, addressing them by identifier. Here's a page which explains it well.
At first you have to copy with the technical stuff.
How to make an AJAX request in the first place?
I would choose the jQuery library. This has a ajax function.
Then you decide:
Do you give back just some data and build the HTML content in JavaScript? (Here again jQuery is a good way to do it)
Or do you build the HTML on the server-side and just put it in my document using the ajax callback method success?
That's for starter

Formatting of JavaScript for MVC3 views

I have tried many different ways to organize the JavaScript that is specific for each View now, but I have not found any way that I feel comfortable with. It seems messy anyway. Probably (and hopefully) that is because I haven't been working with JavaScript very long, and there is a good way of doing this.
Currently what I'm doing is this:
In my Layout file, I have the a RenderSection for scripts in addition to RenderBody. This section contains all JavaScript relevant for each single view. The global script is tucked away in it's own file.
Inside this sections there is a lot of different part of JavaScript (for my biggest View currently there is about 600 lines of JavaScript):
Some definitions of variables and setting different settings (jQuery settings among others).
Hiding different DOM elements on the screen that will be made visible when the user interacts with the View later on.
A lot of jQuery code for different events linked to DOM elements ( click/keyup++ )
Some code refactored into methods because they are used by different jQuery events.
The things I don't like here are mainly two things:
All this code is put into one big codeblock and it's hard to find the parts of the script I'm looking for. Basically, it gets quite unmaintainable as the script grows.
The script is located in the same file as the View. I would like to put the script into a seperate file, but I can't since I use different parts of my Model, and some HtmlHelpers inside the script as well. For example, where I execute some $.post calls, I use #Url.Action('...') to link it to the correct action method to be sure that my script will continue to work even if I change my routing.
I also use the Model to decide if some elements should start out hidden or not like this (is this an ok way to make it start out hidden, or is there a better way? It seems kind of hacky even if I can't put my finger on it).:
code
#if( Model.SomeBoolValue ){
#:$("#DOMelementID").hide();
}
Some pointers to get me in the right direction would be highly appreciated. I need to get this better structured before I lose control over my own code.
I would recommend you taking a look at jQuery plugin authoring and organize your javascript code in terms of plugins that you will attach to your DOM elements.
As far as the #Url.Action('...') problem is concerned there are many ways to solve this issue and externalize your scripts into separate files. For example let's suppose that you are AJAXifying an existing form or ActionLink which already contains the url:
$('#myAnchor').click(function() {
// use this.href to fetch the url
$.post(this.href, function(result) {
});
return false;
});
Now if you simply wanted to send an AJAX request when a user clicks on a div for example, you could always use HTML5 data-* attributes (the same way ASP.NET MVC 3 unobtrusive AJAX and validation works) to define this url on the DOM element:
<div id="mydiv" data-url="#Url.Action("Some Action")">click me</div>
and now in a separate file
$('#mydiv').click(function() {
var url = $(this).data('url');
$.post(url, function(result) {
});
});
and if you follow my first advice about writing a jQuery plugin your code will look like this:
$('#mydiv').myplugin();
Now let's consider the following snippet:
#if(Model.SomeBoolValue) {
#:$("#DOMelementID").hide();
}
So from what it seems here you are using the view model properties to show/hide sections of your code. OK, here's what I may suggest you: JSON serialize this model into the view and then you can start passing values from it to your newly developed jQuery plugins:
<script type="text/javascript">
var model = #Html.Raw(Json.Serialize(Model));
// now configure configure your plugins, for example
$('#mysection').myplugin({ model: model });
</script>
Now inside your plugin you will have access to absolutely all properties of your view model.
And that's all that you will need to put in your view. All the other javascript will be of course in separate javascript files, properly organized in reusable jQuery plugins.
Yep, it can get tough.
Here's what we do, and works for us (in bold, because it may not work for you).
For each View/page, we work out what model properties are required by the JavaScript in order to make decisions (a.k.a "logic").
We also have a section in the Layout for the JavaScript.
We then set a single JavaScript property in the View/page, encapsulating these properties, something like this:
#section JavaScript {
<script type="text/javascript">
yn.yp = {
someBoolValue: #Model.SomeBoolValue,
someOtheProp: '#Model.SomeOtherProp'
}
</script>
}
yn = your namespace, tying the global namespace for your project/company.
yp = your page that your setting the JS property for.
Then in your external JS file:
$(function() {
if (yn.yp.someBoolValue) {
$("#elementid").hide();
}
});
This is also a very clean way to handle routing URL's to be used by client-side AJAX. Setup a property like yn.yp.urls, and set the URL's in there in the View, then the JS can access them easily and without any hard-coding whatsoever.
Overall, the goal here is to reduce server-side code in the embedded page JavaScript.
Set properties for whatever the JS needs to make decisions, then let the JS make the decisions itself.
Hope that makes sense.

Custom View Engine to solve the Javascript/PartialView Issue?

I have seen many questions raised around PartialViews and Javascript: the problem is a PartialView that requires Javascript, e.g. a view that renders a jqGrid:
The partial View needs a <div id="myGrid"></div>
and then some script:
<script>
$(document).ready(function(){
$('#myGrid').jqGrid( { // config params go here
});
}
</script>
The issue is how to include the PartialView without littering the page with inline tags and multiple $(document).ready tags.
We would also like to club the results from multiple RenderPartial calls into a single document.Ready() call.
And lastly we have the issue of the Javascript library files such as JQuery and JQGrid.js which should ideally be included at the bottom of the page (right before the $.ready block) and ideally only included when the appropriate PartialViews are used on the page.
In scouring the WWW it does not appear that anyone has solved this issue. A potential way might be to implement a custom View Engine. I was wondering if anyone had any alternative suggestions I may have missed?
This is a good question and it is something my team struggled with when JQuery was first released. One colleague wrote a page base class that combined all of the document ready calls into one, but it was a complete waste of time and our client's money.
There is no need to combine the $(document).ready() calls into one as they will all be called, one after the other in the order that they appear on the page. this is due to the multi-cast delegate nature of the method and it won't have a significant affect on performance. You might find your page slightly more maintainable, but maintainability is seldom an issue with jQuery as it has such concise syntax.
Could you expand on the reasons for wanting to combine them? I find a lot of developers are perfectionists and want their markup to be absolutely perfect. Rather, I find that when it is good enough for the client, when it performs adequately and displays properly, then my time is better spent delivering the next requirement. I have wasted a lot of time in the past formatting HTML that no-one will ever look at.
Any script that you want to appear at the bottom of the page should go inside the ClientScriptManager.RegisterStartupScript Method as it renders at the bottom of the page.
http://msdn.microsoft.com/en-us/library/z9h4dk8y.aspx
Edit Just noticed that your question was specific to ASP.NET MVC. My answer is more of an ASP.NET answer but in terms of the rendered html, most of my comments are still relevant. Multiple document.ready functions are not a problem.
The standard jQuery approach is to write a single script that will add behaviour to multiple elements. So, add a class to the divs that you want to contain a grid and call a function on each one:
<script language="text/javascript">
$(document).ready(function(){
$('.myGridClass').each(function(){
$(this).jqGrid( {
// config params can be determined from
//attributes added to the div element
var url = $(this).attr("data-url");
});
});
}
</script>
You only need to add this script once on your page and in your partial views you just have:
<div class="myGridClass" data-url="http://whatever-url-to-be-used"></div>
Notice the data-url attribute. This is HTML5 syntax, which will fail HTML 4 validation. It will still work in HTML 4 browsers. It only matters if you have to run your pages through html validators. And I can see you already know about HTML5
Not pretty but as regards your last point can you not send the appropriate tags as a ViewData dictionary in the action that returns the partial?

Categories

Resources