Make Knockout.JS subscribale globally available - asp.net

I have an ASP.NET application and i am using Knockout JS for some minimal Tasks in my application. However i have some EditorTemplates that are calculating values to display. I want access to this values from some other EditorTemplates. I thought i might use the native pub/sub functionality with Knockout, meaning i would write in one EditorTemplate the notification code, like so:
new ko.subscribable().notifySubscribers(this.calculatedValue, "customTopic");
and then receive this value when it changes in my other EditorTemplate
new ko.subscribable().subscribe(function (newValue) {
alert(newValue);
}, this, "customTopic");
Of course, this code wont work, as i am creating a new ko.subscribable() everytime.
How can i create a single ko.subscribable that is available in all my Views?

First you need to define a new instance of subscribable variable globally.
Inside other sub view models subscribe to subscribable variable that you defined.
Notify any subscribers by using notifySubscribers whenever any updates happens.
Example : https://jsfiddle.net/kyr6w2x3/149/
View:
<div id="one">
<h1>MainVM:</h1>
<input type="text" data-bind="textInput:Name">
<hr>
</div>
//-------------------------------------------------
<div id="two">
<h1>SecondVM:</h1>
<div data-bind="text:NameSecondVM"> </div>
</div>
Model:
var shouterValueOfName = new ko.subscribable();
<script type="text/javascript">
var MainViewModel = function(){
var self = this;
self.Name = ko.observable();
self.Name.subscribe(function (newValue) {
newValue = newValue ? newValue + " Sent from MainVM" : "" ;
shouterValueOfName.notifySubscribers(newValue, "secondVMTakeThis");
}, self);
}
</script>
<script type="text/javascript">
var SecondViewModel = function(){
var self = this;
self.SecondVM = ko.observable();
self.NameSecondVM = ko.observable();
shouterValueOfName.subscribe(function (newValue) {
self.NameSecondVM(newValue);
}, this, "secondVMTakeThis");
}
</script>
var viewModelA = new MainViewModel();
var viewModelB = new SecondViewModel();
ko.applyBindings(viewModelA, document.getElementById("one"));
ko.applyBindings(viewModelB, document.getElementById("two"));

Related

ASP.NET MVC: How to send the ids of the DOM Body elements on the client's browser TO the controller when navigating from the view

I am working on an ASP.NET MVC app (ASP.NET NOT ASP.NET Core).
When a View is rendered, the user can click on some buttons on the page to collapse or show divs associated with each button. The div changes its class depending on whether it is collapsed or shown. I am using bootstrap attributes for this, and it works fine.
Now I have a "Save" button on the page. When the user clicks on this button, I need to retrieve the ids and classes of the divs, and pass them TO the Controller (in an array/collection/dictionary whatever).
Is there a way/method in ASP.NET to send to the Controller the attributes (ids, classes, etc) of the DOM elements on the client's browser ?
Thanks
If you want to send some attributes of DOM to Controller, I have a way.
HTML:
<div id="demo-1" class="chosendiv other-className" data-code ="abc">Lorem Ipsum</div>
<div id="demo-2" class="chosendiv other-className" data-code ="xyz">Lorem Ipsum</div>
<div id="demo-3" class="other-className" data-code ="mnt">Lorem Ipsum</div>
<button id="btn-save" onclick="Save()">SAVE</button>
Javascript
<script>
function Save(){
var cds = document.getElementsByClassName('chosendiv');
var finder = [];
if(cds != null){
for(i = 0; i< cds.length; i++){
finder.push({
ID: cds[i].getAttribute('id'),
ClassName: cds[i].getAttribute('class'),
Code: cds[i].getAttribute('data-code')
})
}
}
//
// Send finder to Controller. You can use Ajax...
// A simple ajax call:
//
$.ajax({
url: '/Home/YourAction',
type: 'GET', //<---- you can use POST method.
data:{
myDiv: JSON.stringify(finder)
},
success: function(response){
// Your code
}
})
}
</script>
Your Controller
public class HomeController: Controller
{
public HomeController(){}
[HttpGet]
public void YourAction(string myDiv)
{
//A lot of ways for converting string to Object, such as: creating new class for model, ...
// I use Dictionary Class
List<Dictionary<string, string>> temp = new List<Dictionary<string, string>>();
if(!string.IsNullOrEmpty(myDiv))
{
try
{
temp = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Dictionary<string, string>>>(myDiv);
}
catch { // Do something if it catches error. }
}
// Get a element (at index) from temp if temp.Count()>0
// var id = temp.ElementAt(index)["ID"];
// var className = temp.ElementAt(index)["ClassName"];
// var code = temp.ElementAt(index)["Code"];
//
//Your code
//
}
//......
}
It would be great if my answer could solve your problem.
Based on the answer provided by #Gia Khang
I made few changes in order to avoid the issue of the length of the URL exceeding the maximum limit.
Instead of adding the element's classes to an array using JS, I add them to a string :
function Save() {
var cds = document.getElementsByClassName('chosendiv');
// I use as string instead of an array
var finder = "";
if(cds != null){
for(i = 0; i< cds.length; i++){
finder = finder + "id=" + cds[i].getAttribute('id') + "class=" + cds[i].getAttribute('class') + "data-code=" +cds[i].getAttribute('data-code')
}
}
// Send finder to Controller. You can use Ajax...
// A simple ajax call:
var myURL = "/{Controller}/{Action}"
$.ajax({
url: myURL,
type: "POST",
data: { ids:finder },
success: function (response) {
}
})
}
In the Controller Action I add a parameter named "ids" (this must be the same name as the identifier of the data object in the post request)and I extract the id, class, and data value from the ids string by a method in one of my Models classes (sorry I work with VB.NET not with C# and it will take me a lot of time to convert the code to C#. I use the Split method in VB to split the ids string several times: a first one by using "id=" as delimiter, then spiting each element in the resulting array by the second delimiter "class=", etc. I add the resulting elements to a collection)
The Controller Action looks like this:
public class HomeController: Controller
{
public HomeController(){}
[HttpPost]
public void YourAction(string ids)
{
Models.myClass.splitStringMethod(ids)
Return View()
}
}

data bind weather id value to i class in mvc razor view using KO

Just trying to implement a weather forecast in my MVC5 App View using KO.
To be able to display the weather icon I have to fetch the id from the observable weather array, which I am able to obtain as follows:
<span data-bind="text:weather()[0].id"></span>
Then I have to put the id value of a particular city eg "801" in the i class like below:
<i class="wi wi-owm-ID value here+ "></i>
to display as
<i class="wi wi-owm-801"></i>
I am using the weather class
public Object getWeatherForcast()
{
string appid = "xxxxxxxxxxx";
string url = "http://api.openweathermap.org/data/2.5/weather?q=Auckland&APPID=" + appid + "&units=metric";
var client = new WebClient();
var content = client.DownloadString(url);
var serializer = new JavaScriptSerializer();
var jsonContent = serializer.Deserialize<Object>(content);
return jsonContent;
}
Everything works I am just struggling to display the weather Id in the so that an icon could be picked from the css file. Any help will be appreciated
You can make class observable with Knockout, like this:
<i data-bind="attr: {class: 'wi wi-owm-'+weather()[0].id()}"></i>
or
<i data-bind="css: 'wi wi-owm-'+weather()[0].id()"></i>
Check fiddle: Fiddle
You can calculate css class in the model and pass it to the markup via "css" binding:
function model() {
var result = {
id: 15,
style: undefined
};
result.style = ko.computed(function() {
return "some-prefix-" + result.id;
});
return result;
};
ko.applyBindings(new model());
.some-prefix-15 {
width: 30px;
height: 15px;
border: 1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="css: style"></div>

Knockout 3.2 component data context

I am using Knockout 3.2 and the new component system. I am trying to have components that include sub-components.
Home Page (component - with HomePageViewModel)
NewsFeed1 (component with HomePageViewModel.NewsFeedViewModel1)
NewsFeed2 (component with HomePageViewModel.NewsFeedViewModel2)
HomePageViewModel
var viewModel = (function () {
function viewModel() {
this.message = ko.observable("Welcome to DKT!");
this.newsFeedViewModel = new gr.viewModel();
this.newsFeedViewModel2 = new gr.viewModel();
this.newsFeedViewModel.message("Message 1");
this.newsFeedViewModel2.message("Message 2");
}
return viewModel;
})();
NewsFeedViewModel
var viewModel = (function () {
function viewModel() {
this.message = ko.observable("This is the profile!");
}
return viewModel;
})();
As you can see the HomePageViewModel contains both the NewsFeedViewModel. I now want to be able to use these as the DataContext/BindingContext of my two components but this does not seem to work.
Home.html
<news-feed data-bind="newsFeedViewModel"></news-feed>
<news-feed data-bind="newsFeedViewModel2"></news-feed>
Both these components do not use the ViewModels from the HomePageViewModel but uses a new NewsFeedViewModel. How can I make the datacontext of both these components bind to the viewModels stored in the top component (home)?
Generally, you would want to supply your component with any data via params. For example, with your structure, you could create the component like:
ko.components.register("news-feed", {
viewModel: function (params) {
this.vm = params.vm;
},
template: "<h2>News Feed</h2><div data-bind=\"text: vm.message\"></div>"
});
Then, you would define the elements like:
<news-feed params="vm: newsFeedViewModel"></news-feed>
<news-feed params="vm: newsFeedViewModel2"></news-feed>
You could choose to pass the message in directly for each and/or choose whatever names make sense for your params (rather than vm).
Sample: http://jsfiddle.net/rniemeyer/fssXE/

RenderPartial will not fire controller ActionResult Method

I have a Razor view containing a partial view that I want to update when the user clicks the refresh button on my pop up
The code executes with no errors, but my breakpoint in the controller method PricingUpdate does not fire. If I add alerts to my JavaScript this tells me the Javascript is firing OK
I can't see anything wrong with what I have, I am suspicious of the url variable string value, i.e. var url = 'Supplypoint/PricingUpdate'; but i've tried many variations
My Controller Method :
public ActionResult PricingUpdate(DateTime StartDate,DateTime EndDate, int SupplyPointId)
{
var obj = _db.GetSupplyPoint(SupplyPointId);
_db.SupplyPointCalculateWastePricing(obj, StartDate, EndDate);
_db.SupplyPointCalculatePricing(obj, StartDate, EndDate);
var supplyPoint = _db.GetSupplyPoint(SupplyPointId);
return PartialView("_DetailsPricing", supplyPoint);
}
My main View code extract :
<div id="ResultsList" style="clear:both;">
#{Html.RenderPartial("_DetailsPricing", Model);}
</div>
My script code in the main View :
$("#RefreshBtn").click(function () {
var url = 'Supplypoint/PricingUpdate';
var data = {
StartDate: $('#StartDate').val(),
EndDate: $('#EndDate').val(),
SupplyPointId: $('#SupplyPointId').val().toString()
};
$("#ResultsList").load(url,data,function () {
$('#LoadingGif').empty();
});
$('#LoadingGif').empty().html('<img src="/Content/images/ajax-loader.gif" width=31 height=31 alt="Loading image" />');
});
Yikes I had called my date inputs by the wrong name, changed code to : (all ok now)
var data = {
StartDate: $('#from').val(),
EndDate: $('#to').val(),
SupplyPointId: $('#SupplyPointId').val().toString()
};

asp.net mvc - how to update dropdown list in tinyMCE

Scenario: I have a standard dropdown list and when the value in that dropdownlist changes I want to update another dropdownlist that exists in a tinyMCE control.
Currently it does what I want when I open the page (i.e. the first time)...
function changeParent() {
}
tinymce.create('tinymce.plugins.MoePlugin', {
createControl: function(n, cm) {
switch (n) {
case 'mylistbox':
var mlb = cm.createListBox('mylistbox', {
title: 'Inserts',
onselect: function(v) {
tinyMCE.execCommand("mceInsertContent",false,v);
}
});
<% foreach (var insert in (ViewData["Inserts"] as List<String>)) { %> // This is .NET
yourobject = '<%= insert %>'; // This is JS AND .NET
mlb.add(yourobject, yourobject); // This is JavaScript
<% } %>
// Return the new listbox instance
return mlb;
}
return null;
}
});
<%= Html.DropDownList(Model.Record[184].ModelEntity.ModelEntityId.ToString(), ViewData["Containers"] as SelectList, new { onchange = "changeParent(); return false;" })%>
I am thinking the way to accomplish this (in the ChangeParentFunction) is to call a controller action to get a new list, then grab the 'mylistbox' object and reassign it, but am unsure how to put it all together.
As far as updating the TinyMCE listbox goes, you can try using a tinymce.ui.NativeListBox instead of the standard tinymce.ui.ListBox. You can do this by setting the last argument to cm.createListBox to tinymce.ui.NativeListBox. This way, you'll have a regular old <select> that you can update as you normally would.
The downside is that it looks like you'll need to manually hook up your own onchange listener since NativeListBox maintains its own list of items internally.
EDIT:
I played around a bit with this last night and here's what I've come up with.
First, here's how to use a native list box and wire up our own onChange handler, the TinyMCE way:
// Create a NativeListBox so we can easily modify the contents of the list.
var mlb = cm.createListBox('mylistbox', {
title: 'Inserts'
}, tinymce.ui.NativeListBox);
// Set our own change handler.
mlb.onPostRender.add(function(t) {
tinymce.dom.Event.add(t.id, 'change', function(e) {
var v = e.target.options[e.target.selectedIndex].value;
tinyMCE.activeEditor.execCommand("mceInsertContent", false, v);
e.target.selectedIndex = 0;
});
});
As far as updating the list box at runtime, your idea of calling a controller action to get the new items is sound; I'm not familiar with ASP.NET, so I can't really help you there.
The ID of the <select> that TinyMCE creates takes the form editorId_controlId, where in your case controlId is "mylistbox". Firebug in Firefox is the easiest way to find the ID of the <select> :)
Here's the test button I added to my page to check if the above code was working:
<script type="text/javascript">
function doFoo() {
// Change "myEditor" below to the ID of your TinyMCE instance.
var insertsElem = document.getElementById("myEditor_mylistbox");
insertsElem.options.length = 1; // Remove all but the first option.
var optElem = document.createElement("option");
optElem.value = "1";
optElem.text = "Foo";
insertsElem.add(optElem, null);
optElem = document.createElement("option");
optElem.value = "2";
optElem.text = "Bar";
insertsElem.add(optElem, null);
}
</script>
<button onclick="doFoo();">FOO</button>
Hope this helps, or at least gets you started.
Step 1 - Provide a JsonResult in your controller
public JsonResult GetInserts(int containerId)
{
//some code to get list of inserts here
List<string> somedata = doSomeStuff();
return Json(somedata);
}
Step 2 - Create javascript function to get Json results
function getInserts() {
var params = {};
params.containerId = $("#184").val();
$.getJSON("GetInserts", params, updateInserts);
};
updateInserts = function(data) {
var insertsElem = document.getElementById("183_mylistbox");
insertsElem.options.length = 1; // Remove all but the first option.
var optElem = document.createElement("option");
for (var item in data) {
optElem = document.createElement("option");
optElem.value = item;
optElem.text = data[item];
try {
insertsElem.add(optElem, null); // standards compliant browsers
}
catch(ex) {
insertsElem.add(optElem, item+1); // IE only (second paramater is the items position in the list)
}
}
};
Step 3 - Create NativeListBox (code above provided by ZoogieZork above)
var mlb = cm.createListBox('mylistbox', {
title: 'Inserts'
}, tinymce.ui.NativeListBox);
// Set our own change handler.
mlb.onPostRender.add(function(t) {
tinymce.dom.Event.add(t.id, 'change', function(e) {
var v = e.target.options[e.target.selectedIndex].value;
tinyMCE.activeEditor.execCommand("mceInsertContent", false, v);
e.target.selectedIndex = 0;
});
});
//populate inserts on listbox create
getInserts();

Resources