How to load initial data from rails to redux - ruby-on-rails

I tried to load json from rails, and pass it to redux createStore.
let initialStore = window.INITIAL;
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const store = createStoreWithMiddleware(rootReducer, initialStore);
But i always get undefined, or just window.INITIAL return value after store.
At first, store loads with empty object, then fetch action is dispatched, and i get updated store with json, but i already got error, when i'm trying to call something like { product.title } on empty object. No matter what I do, i can't load json before redux stuff begins, even with global data, like this.
(function() {
$.getJSON('http://localhost:3000/products.json', function(data) {
return window.INITIAL = data;
});
});
Api and controller is simple.
def index
products = Product.all
render json: products
end
How do you handle this? I want to do it without any gems like react-rails etc, i can't pass all initial state to Root component in one place.

In the question above you have the following:
let initialStore = window.INITIAL;
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const store = createStoreWithMiddleware(rootReducer, initialStore);
The problem with the above code is that it will only populate your store with window.INITIAL as it exists when the page first loads. Any further changes to window.INITIAL will be ignored.
What you need to do is populate window.INITIAL with your initial data in your server rendered code. That is, simply place the script block before your redux code:
<script>
var INITIAL = { /* this object should match the shape of your reducer state */ };
</script>
That's it. Now get rid of the AJAX call. You don't need it. This will be a lot more performant too. Instead of forcing the user to wait on an additional request after the page has already rendered, the user instead gets all the data at the same time as the rest of the page.

Related

Knockout mapping is not updating my model

I'm having trouble with a knockout model that is not binding on a subscribed update. I have a C# MVC page that delivers a model to the template which is parsed to Json and delivered raw as part of a ViewModel assignment for ko.applyBindings. I have a subscription to an observable that calls a method to perform an update of the viewModel's data. Irrelevant stuff pulled out and renamed for example usage:
var myViewModel = function (data) {
var self = this;
self.CurrentPage = ko.observable();
self.SomeComplexArray= ko.observableArray([]);
self.Pager().CurrentPage.subscribe(function (newPage) {
self.UpdateMyViewModel(newPage);
});
self.UpdateMyViewModel= function (newPage) {
var postData = { PageNumber: newPage };
$.post('/Article/GetMyModelSearchByPage', postData, function (data) {
ko.mapping.fromJS(data, {}, self);;
});
};
When I perform logging, I can see all of the data, and it all looks correct. The same method is used to produce both the initial model and the updated model. I've used this technique on other pages and it worked flawlessly each time. In this case however, I'm looking for it to bind/update SomeComplexArray, and that's just not happening. If I attempt to do it manually, I don't get a proper bind on the array I get blank. I'm wondering if there is something obvious that I'm doing wrong that I'm just flat out missing.
Edit: I don't know that ko.mapping can be pointed to as the culprit. Standard model changes are also not affecting the interface. Here is something that is not working in a bound sense. I have a p element with visible bound to the length of the array and a div element with a click bound to a function that pops items off of SomeComplexArray. I can see in the console log that it is performing its function (and subsequent clicks result in 'undefined' not having that function). However, the p element never displays. The initial array has only 2 items so a single click empties it:
<p data-bind="visible: SomeComplexArray().length === 0">nothing found</p>
<div data-bind="click: function() { UpdateArray(); }">try it manually</div>
-- in js model
self.UpdateArray = function () {
console.log(self.SomeComplexArray());
console.log(self.SomeComplexArray().pop());
console.log(self.SomeComplexArray());
console.log(self.SomeComplexArray().pop());
console.log(self.SomeComplexArray());
});
Edit 2: from the comment #Matt Burland, I've modified how the pop is called and the manual method now works to modify the elements dynamically. However, the ko.mapping is still not functioning as I would expect. In a test, I did a console.log of a specific row before calling ko.mapping and after. No change was made to the observableArray.
I created a test of your knockout situation in JSFiddle.
You have to call your array function without paranthesis. I tested this part:
self.UpdateArray = function () {
self.SomeComplexArray.pop();
};
It seems to be working on JSFiddle side.
I'm not really sure why, but it would seem that ko.mapping is having difficulty remapping the viewmodel at all. Since none of the fields are being mapped into self my assumption is that there is an exception occurring somewhere that ko.mapping is simply swallowing or it is not being reported for some other reason. Given that I could manually manipulate the array with a helpful tip from #MattBurland, I decided to backtrack a bit and update only the elements that needed to change directly on the data load. I ended up creating an Init function for my viewModel and using ko.mapping to populate the items directly there:
self.Init = function (jsonData) {
self.CurrentPage(0);
self.Items(ko.mapping.fromJS(jsonData.Items)());
self.TotalItems(jsonData.TotalItems);
// More stuff below here not relevant to question
}
The primary difference here is that the ko.mapping.fromJS result needed to be called as a function before the observableArray would recognize it as such. Given that this worked and that my controller would be providing an identical object back during the AJAX request, it was almost copy/past:
self.UpdateMyViewModel= function (newPage) {
var postData = { PageNumber: newPage };
$.post('/Article/GetMyModelSearchByPage', postData, function (data) {
self.Items(ko.mapping.fromJS(JSON.parse(data).Items)());
});
};
This is probably not ideal for most situations, but since there is not a large manipulation of the viewModel occurring during the update this provides a working solution. I would still like to know why ko.mapping would not remap the viewModel at the top level, but in retrospect it probably would have been a disaster anyway since there was "modified" data in the viewModel that the server would have had to replace. This solution is quick and simple enough.

knockout js binding limitation on complex view model

I am 3 months into learning KnockoutJS and it has been great so far. However, I am facing an issue with binding.
This is the scenario:
I am using MVC with KO.
MVC model is passed down to the view, converted into a knockout object and pushed into the viewModel variable:
var data = ko.mapping.fromJS(#Html.Raw(Json.Encode(Model)));
var viewModel = new HP.ViewModels.CertificationPathViewModel(data);
ko.applyBindings(viewModel);
Within viewModel, I reference the MVC model as self.data:
ViewModels.CertificationPathViewModel = (function (data) {
var self = ViewModels.BaseEntityViewModel.apply(this, [data]);
// some other code
return { Data: self.Data, };
}
ViewModels.BaseEntityViewModel = (function (data) {
var self = this;
self.data = ko.observable(data);
// other code
return { Data: self.data, };
}
On the view, I data-bind like this:
<div id="drpControl" data-bind="CustomDropdown: Data().BusinessUnits.SelectedGroup, optionSettings: { CustomOptions: Data().Units.Groups, CustomOptionsCaption: '-- Select Group --' }"></div>
I try to update the self.data after an ajax call. I return the entire MVC model object and attempt to replace self.data like this :
self.data(updatedModel)
My expectation is that KO will take care of the update and no extra binding is needed. It works great for simple binding (ex. Value: Data().Something) but it doesn't work for complex binding (ex. value: Data().BusinessUnits.SelectedGroup ).
The controls that have complex binding are still bound to the old model, so KO doesn't know what to pass back next time I submit an ajax request.
Is this a limitation of KO, or I am not doing something properly?
Thanks
the ko.mapping plugin changes every property on self.data into an observable. During your update, you need to remap the updated data.
Since you didn't actually post your code, just unformatted snippets I can't help a whole bunch, but you should start by changing this line: self.data(updatedModel) to this:
ko.mapping.fromJS(updatedModel, self.data);
see the Knockout.JS mapping documentation
Protip for stack overflow - include your full code, to the extent that it's possible. Also, if you can, make a jsfiddle that reproduces your problem.

Rendering dynamic scss-files with ajax, rails

As the title suggests, my main objective is to render a dynamic scss(.erb) file after an ajax call.
assets/javascripts/header.js
// onChange of a checkbox, a database boolean field should be toggled via AJAX
$( document ).ready(function() {
$('input[class=collection_cb]').change(function() {
// get the id of the item
var collection_id = $(this).parent().attr("data-collection-id");
// show a loading animation
$("#coll-loading").removeClass("vhidden");
// AJAX call
$.ajax({
type : 'PUT',
url : "/collections/" + collection_id + "/toggle",
success : function() {
// removal of loading animation, a bit delayed, as it would be too fast otherwise
setTimeout(function() {
$("#coll_loading").addClass("vhidden");
}, 300);
},
});
});
});
controller/collections_controller.rb
def toggle
# safety measure to check if the user changes his collection
if current_user.id == Collection.find(params[:id]).user_id
collection = Collection.find(params[:id])
# toggle the collection
collection.toggle! :auto_add_item
else
# redirect the user to error page, alert page
end
render :nothing => true
end
All worked very smooth when I solely toggled the database object.
Now I wanted to add some extra spices and change the CSS of my 50+ li's accordingly to the currently selected collections of the user.
My desired CSS looks like this, it checks li elements if they belong to the collections and give them a border color if so.
ul#list > li[data-collections~='8'][data-collections~='2']
{
border-color: #ff2900;
}
I added this to my controller to generate the []-conditions:
def toggle
# .
# .
# toggle function
# return the currently selected collection ids in the [data-collections]-format
#active_collections = ""
c_ids = current_user.collections.where(:auto_add_item => true).pluck('collections.id')
if c_ids.size != 0
c_ids.each { |id| #active_collections += "[data-collections~='#{id}']" }
end
# this is what gets retrieved
# #active_collections => [data-collections~='8'][data-collections~='2']
end
now I need a way to put those brackets in a scss file that gets generated dynamically.
I tried adding:
respond_to do |format|
format.css
end
to my controller, having the file views/collections/toggle.css.erb
ul#list<%= raw active_collections %> > li<%= raw active_collections %> {
border-color: #ff2900;
}
It didn't work, another way was rendering the css file from my controller, and then passing it to a view as described by Manuel Meurer
Did I mess up with the file names? Like using css instead of scss? Do you have any ideas how I should proceed?
Thanks for your help!
Why dynamic CSS? - reasoning
I know that this should normally happen by adding classes via JavaScript. My reasoning to why I need a dynamic css is that when the user decides to change the selected collections, he does this very concentrated. Something like 4 calls in 3 seconds, then a 5 minutes pause, then 5 calls in 4 seconds. The JavaScript would simply take too long to loop through the 50+ li's after every call.
UPDATE
As it turns out, JavaScript was very fast at handling my "long" list... Thanks y'all for pointing out the errors in my thinking!
In my opinion, the problem you've got isn't to do with CSS; it's to do with how your system works
CSS is loaded static (from the http request), which means when the page is rendered, it will not update if you change the CSS files on the server
JS is client side and is designed to interact with rendered HTML elements (through the DOM). This means that JS by its nature is dynamic, and is why we can use it with technologies like Ajax to change parts of the page
Here's where I think your problem comes in...
Your JS call is not reloading the page, which means the CSS stays static. There is currently no way to reload the CSS and have them render without refreshing (sending an HTTP request). This means that any updating you do with JS will have to include per-loaded CSS
As per the comments to your OP, you should really look at updating the classes of your list elements. If you use something like this it should work instantaneously:
$('li').addClass('new');
Hope this helps?
If I understood your feature correctly, actually all you need can be realized by JavaScript simply, no need for any hack.
Let me organize your feature at first
Given an user visiting the page
When he checks a checkbox
He will see a loading sign which implies this is an interaction with server
When the loading sign stopped
He will see the row(or 'li") he checked has a border which implies his action has been accepted by server
Then comes the solution. For readability I will simplify your loading sign code into named functions instead of real code.
$(document).ready(function() {
$('input[class=collection_cb]').change(function() {
// Use a variable to store parent of current scope for using later
var $parent = $(this).parent();
// get the id of the item
var collection_id = $parent.attr("data-collection-id");
show_loading_sign();
// AJAX call
$.ajax({
type : 'PUT',
url : "/collections/" + collection_id + "/toggle",
success : function() {
// This is the effect you need.
$parent.addClass('green_color_border');
},
error: function() {
$parent.addClass('red_color_border');
},
complete: function() {
close_loading_sign(); /*Close the sign no matter success or error*/
}
});
});
});
Let me know if my understanding of feature is correct and if this could solve the problem.
What if, when the user toggles a collection selection, you use jquery change one class on the ul and then define static styles based on that?
For example, your original markup might be:
ul#list.no_selection
li.collection8.collection2
li.collection1
And your css would have, statically:
ul.collection1 li.collection1,
ul.collection2 li.collection2,
...
ul.collection8 li.collection8 {
border-color: #ff2900;
}
So by default, there wouldn't be a border. But if the user selects collection 8, your jquery would do:
$('ul#list').addClass('collection8')
and voila, border around the li that's in collection8-- without looping over all the lis in javascript and without loading a stylesheet dynamically.
What do you think, would this work in your case?

JQM - Inject dynamic content at load time only

I'm trying to dynamically populate a select tag at load time (latest jQM version) using a custom template filling function.
If the fn is called in the "pagebeforechange" event, the select tag is properly initialized. Since this event is called on every page transition, I thought of moving the fn to the 'pageinit' event. This does not work, presumably because the DOM is not yet fully available. How can I coerce jQM to inject content in a page only once? Currently, I am using a kludge. There surely must be a smarter way. Thanks for any suggestions.
$(document).bind('pageinit', function () {
InitSelTagTest("#selActTag", "tplTag"); // Does not work.
});
$(document).bind("pagebeforechange", function (e, data) {
if ($("#selActTag").children().size() === 0) {
InitSelTagTest("#selActTag", "tplTag"); // Kludge, but it works
}
});
function InitSelTagTest(el,tpl) { // Append all tags to element el
var lstAllTags = JSON.parse($("#hidTag").val()); // Create tag array
// Retrieve html content from template.
var cbeg = "//<![" + "CDATA[", cend = "//]" + "]>";
var rslt = tmpl(tpl, { ddd: lstAllTags }).replace(cbeg, ").replace(cend,");
$(el).html(rslt).trigger("create"); // Add to DOM.
}
EDIT
In response to Shenaniganz' comment, it seems that the "pagebeforecreate" event could do the trick ie.
$("#pgAct").live("pagebeforecreate", function () {
// Populate tag select. Works. Traversed only once.
InitSelTag("#selActTag", "tplTag");
});
I'm not sure I fully understand your question but I'll throw a few things out there and you let me know if I can extend further.
To make something trigger only once on page load you can try to implement a regular JQuery $(document).ready(function(){}) aka $(function(){}) for the exact reason why JQuery Mobile users are told not to use it. It triggers only once on DOM load. Further pages don't trigger it because they're being switched via Ajax.
Other than that, on regular dynamic content loading you take a look at the following example I put together for someone else earlier:
http://jsbin.com/ozejif/1/edit

Ajax queue Backbone js

I am running Backbone js 0.9.2 on Rails 3.2.2,I have a page for adding cost rows.A cost have 3 TextFields: title, description and price.
I am saving each cost on blur.
model.save() gets called multiple times with very short intervals. Which issues one create(post) request then one update(put) request shortly there after. The problem I am experiencing is that PUT request sometimes reaches the server before the POST, the result being that model gets created and persisted twice(duplicates).
To save on blur is the requested behavior, so I need a way to queue up requests.
I have read something about Spine js, and that they solve it by some kind of queue. I've also looked in to this, but can't seem to figure this out.
It feels like this should be a common issue, working with "single-page-apps" but can't find anything about it.
You could override the save method and create a queue with a deferred object . For example,
var MDef = Backbone.Model.extend({
url: "/echo/json/?delay=3",
initialize: function() {
this.queue = $.Deferred();
this.queue.resolve();
},
save: function(attrs,options) {
var m = this;
console.log("set "+JSON.stringify(attrs));
// this.queue = this.queue.pipe with jquery<1.8
this.queue = this.queue.then(function() {
console.log("request "+JSON.stringify(attrs));
return Backbone.Model.prototype.save.call(m, attrs, options);
});
}
});
var m = new MDef();
m.save({title: "a title"});
m.save({description: "a description"});
m.save({price: "a price"});
And a Fiddle : http://jsfiddle.net/nikoshr/8nEUm/
User debounce from underscore.js.
Creates and returns a new debounced version of the passed function that will postpone its execution until after wait milliseconds have elapsed since the last time it was invoked.
This way it will only fire once after the last blur event.

Resources