I am coloring each user's text submissions to my application based on a color they choose. To do this, I dynamically add rules to a stylesheet as new users choose their color.
The problem I'm trying to solve is updating this color using AJAX without reloading the page.
I apply the color by assigning a class that corresponds to each user and the story they are submitting to:
<span class="<%= "contrib-u#{snippet.user.id}-s#{#story.id}" %>">
(code to display text submission)
</span>
I create classes for each user that match this contrib-u1-s2 format (contribution for user 1, story 2) using JQuery once when the application loads:
$(document).one 'turbolinks:load', ->
build_contribution_styles()
build_contribution_styles = ->
req = $.get('/contributions/index')
req.done (data) ->
user_sheet = document.styleSheets[5]
for contribution in data
user_sheet.addRule(
".contrib-u" + contribution['user_id'] + "-s" + contribution['story_id'],
"color: #" + contribution['color']
)
So far, this works great. Each user's submission for each story is correctly colored based on these dynamically created classes.
Next, I provide the user a text field so they can select a new color for their submissions:
<%= text_field_tag :snippet_color,
current_user.get_contribution_color(#story),
class: "jscolor centered-text",
data: {user_id: current_user.id, story_id: #story.id}
%>
Then, in JQuery, I attach an event listener to this text field to update the database and the classes with the newly selected color:
$(document).on 'turbolinks:load', ->
$('#snippet_color').change (e) -> handle_snippet_color_change(e, #)
handle_snippet_color_change = (e, input) ->
data = {
contribution: {
story_id: $(input).data('story-id'),
user_id: $(input).data('user-id'),
color: $(input).val()
}
}
req = $.post('/contributions/update', data)
req.done (data) ->
els = $('.contrib-u' + data['user_id'] + '-s' + data['story_id'])
els.css('color', data['color'])
This also works nicely, but only after I reload the page. The updates to the class are not applied to the elements until then.
This is somewhat confusing to me, because there are a number of other cases where I change the CSS style of an element dynamically with javascript and it changes without any page refresh. For instance, many times in this application I select an element and toggle its display property to hide it or show it.
I've found a few sources of information about "reloading a CSS style without a page refresh", but none of them seem to correct this particular problem. One suggestion was to place these new CSS rules into a style tag on the page with these elements to get them to automatically updated without a reload, but that does not appear to make any difference in my case.
EDIT: Working Solution
I have it working now by removing the previous rule and adding an updated rule to the stylesheet:
req = $.post('/contributions/update', data)
req.done (data) ->
selector = '.contrib-u' + data['user_id'] + '-s' + data['story_id']
sheet = document.styleSheets[5]
rules = sheet.cssRules
for i in [0...rules.length]
if rules[i].selectorText is selector
sheet.deleteRule(i)
sheet.addRule(selector, "color: #" + data['color'])
The problem was that I was just updating the styles for the existing elements. I'm still not sure why their updated styles were not applied immediately, but this new approach achieves the desired functionality.
Related
I have an ASP.NET project using .NET 4.5 and VisualBasic with Bootstrap 3.7. On a particular page a dynamic html table is created in the MainContent container of the page based on a selection the user makes. The data for that table is drawn from a SQL Server database. In selected cells of the table an image is added (dynamically at the same time the table is created) with a javascript function call to create and open a bootstrap popover. The javascript function is located inside the MainContent container of the page. 3 parameters are sent to the function - the id of the image, the title, and content applicable to this image. All 3 values are sent as string values.
The popover works exactly as I expect it to except it does not populate the content. As I click from 1 popover image to another the appropriate title for each different popover is displayed and the popover is located in the correct location. I know the data is sent and received by the javascript function. If in the JavaScript function I change document.getElementById(ctrlTransfer) to 'image' the popover displays both the correct title and content. (It is not positioned by the particular popover image that was clicked but instead, by the 1st non-dynamic image on the page. Also, clicking on other popover images does not change the title or content when 'image' has been substituted.)
Here is the JavaScript function
<script type="text/javascript">
function DisplayTransferValues(TransferControl, TransferTitle, TransferValue) {
if (TransferTitle != 'undefined') {
var ctrlTransfer = 'MainContent_' + TransferControl;
$document.getElementById(ctrlTransfer)).popover({
trigger: 'hover',
html: true,
title: TransferTitle,
content: TransferValue,
}).popover("show");
}
}
</script>
The html table is created by a call to an ASP.NET sub (in an independent code module in the project) that selectively places a popover image in certain cells in the table.
The code for creating the images for the popover follows:
Dim ibnTrnsfr As ImageButton = New ImageButton
ibnTrnsfr.ID = "ibnTrnsfr" & strCntrlCounter
ibnTrnsfr.Height = 12
ibnTrnsfr.Width = 12
If Not arrScheming(intRow, intCol, 25).Equals(DBNull.Value) Then
ibnTrnsfr.ImageUrl = "/Images/TransferOutBlue4.png"
strTransfrOut = "Transfer Out of " & strCntrlCounter
ibnTrnsfr.OnClientClick = "DisplayTransferValues('" & ibnTrnsfr.ID & "', '" & strTrnsfrOut & "', '" & arrScheming(intRow, intCol, 25) & "'); return false;"
ElseIf Not arrScheming(intRow, intCol, 26).Equals(DBNull.Value) Then
ibnTrnsfr.ImageUrl = "/Images/TransferOutBlue4.png"
strTransfrIn = "Transfer into " & strCntrlCounter
ibnTrnsfr.OnClientClick = "DisplayTransferValues('" & ibnTrnsfr.ID & "', '" & strTrnsfrIn & "', '" & arrScheming(intRow, intCol, 26) & "'); return false;"
End If
Additional code in the page code behind actually adds this image to the table cell.
Steps I have already taken:
Added a function within the DisplayTransferValues function to receive data for content and populate.
Added a delay to DisplayTransferValues for loading time.
Switched the order of "TransferTitle" and "TransferValue" in the parameters sent to the function. It successfully loads the correct data from TransferValue into the title area of the popover.
I am looking to have both the title and content populated in the popover.
Thanks for the help.
I did some additional research and found at getbootstrap.com sometimes styles for a parent element can interfere with html inside the popover. Simply adding container: 'body', fixed the issue:
<script type="text/javascript">
function DisplayTransferValues(TransferControl, TransferTitle, TransferValue) {
if (TransferTitle != 'undefined') {
var ctrlTransfer = 'MainContent_' + TransferControl;
$document.getElementById(ctrlTransfer)).popover({
container: ' body',
trigger: 'hover',
html: true,
title: TransferTitle,
content: TransferValue,
}).popover("show");
}
}
</script>
Now both the Title and the Content are populated correctly in the Bootstrap Popover.
I have a chartist.js bar graph. I want to customize x-axis labels. I wrote some following jquery for flipping first and last name and then add '...' at the end if the length of the text is more than 11 characters.
$(function () {
$('#AssignedLineChart .ct-labels, #ResolvedBarChart .ct-labels').find('.ct-label.ct-horizontal').each(function () {
var label = $(this).text();
var splitLabel = label.split(",");
var newLabel = splitLabel[1] + ", "+splitLabel[0];
if (newLabel.length > 13) {
newLabel = newLabel.substring(0, 10) + "...";
}
$(this).text(newLabel);
});
});
It applied fine when I load the page first time. There are some select options on bar charts for displaying individual ranges. When I select them the labels go back to their previous state. Selecting options are changing DOM. This also happens when I open inspect element tab.
Is there a way to use find or each method on dynamically changed elements?
$(function () {
This is only applied when the page finishes loading and get fired 1 time only.
Selecting options are changing DOM.
So the current process is like:
Page finishes loading.
The label got changed by document ready event. (*)
The label got changed by other events, this case, the select options. (**)
So, it changed the DOM dynamically and nothing like above (*) changes the label again.
So, to change the label after select options, you need to change it in the callback function of what is done after select options in the above mentioned (**).
I am designing a website that shows the availability of a resource using either a green or red colour indicator based upon the availability field of a connected MySQL database table.
The item I am looking to alter is a span element:
<span class="equipment" data-id="1" data-available="1" data-location="0-0"></span>
This is being parsed by JQuery for the data attributes for availability and location, and compared to a MySQL database with AJAX post to note changes in availability which should be propagated to the webpage, changing the colour of the indicator as per the below CSS.
.equipment[data-available='1'] {
background-color: rgb(0,226,0); //green
}
.equipment[data-available='0'] {
background-color: rgb(226,0,0); //red
}
The AJAX request, seen below, recognizes changes from the database and returns from the php file successfully, returning just the new availability (0 or 1). If I console.log() the php post URL, the value returned by equipment_span.data("available") and stored in old_avail appears to have updated to the new value desired after a database change occurs. However, the changes to this aren't reflected in a colour change to the indicator.
function update_avail() {
$('.equipment').each( function () {
var equipment_span = $(this);
var old_avail = equipment_span.data("available");
var loc = equipment_span.data("location");
$.post('avail.php?a='+old_avail+'&l='+loc, function(new_avail) {
if(new_avail != old_avail) {
equipment_span.data("available", new_avail);
}
})
})
}
If anyone could offer any pointers as to what is going wrong, that would be great as this has been annoying me for hours at this stage.
It is because of this line:
equipment_span.data("available", new_avail);
When jquery manages the data attributes it does so in memory and not "on the page", so if you inspect the page it will show data-available="whatever it was on page load"
You need to do:
equipment_span.attr("data-available", new_avail);
I am trying to create a grid that has a column where the editor is always available, so that editing the cell is a "one click" process. By this I mean rather than having to click on the cell to first switch to edit mode, and then select from the combo box, the user can straight away (using the mouse) click on the combobox down arrow to open it and select a value.
I thought I could do this using a column template (as opposed to editor) as follows...
function createComboTemplate(dataItem) {
var tmpl = '<input style="width:100%" ' +
'kendo-combo-box ' +
'k-data-text-field="\'display\'" ' +
'k-data-value-field="\'rego\'" ' +
'k-data-source="getCarList()"' +
'k-value="dataItem.rego"' +
'k-on-change="handleDDLChange(kendoEvent, dataItem)"/>';
return tmpl;
}
Full code here
The above shows the combo box, however as soon as I click on it, the cell goes to a text edit field. So I thought that perhaps the cell going into edit mode was causing this, so I set the columns editable property to false , but this made no difference.
IF I set the whole grid's editable property to false, then when I click on the combo box, it stays there, however it is empty.
In this example, the combobox data source is via a function, I also tried setting directly to a global list object (incase it was the function call that was the problem), but this didn't work either.
So, I Have a couple of related questions here.
The first, is to do with the property names in the template.
When I create a combobox in straight code, I have as follows (as in the above demo)
function createCombo(container, options, data) {
var dataField = options.field.split('.');
var fieldName = dataField[0];
var input = $('<input/>')
input.appendTo(container)
input.kendoComboBox({
autoBind: true,
filter: "contains",
placeholder: "select...",
suggest: true,
dataTextField: "display",
dataValueField: "rego",
dataSource: data,
value: options.model[fieldName].rego,
change: function (e) {
var dataItem = this.dataItem();
options.model[fieldName]['rego'] = dataItem.rego;
options.model.set(fieldName + '.display', dataItem.display);
}
});
}
So the above snippet has properties like "dataTextField", and "dataSource", etc, but when I created the template, from another example of templates I found, it seemed to use names like "k-data-text-field" and "k-data-source".
Is there any doco, or rules on how these field names map in the "markup" that is used in the templates (I could not find any)? It appear that the property names are prefixed with "k-data", and then the camelcase names converted to the "dash" syntax (similar to what angular does). IS this just the rules that we follow? If not then perhaps my problems are the syntax above is incorrect.
The other question is of course, what have I done wrong to cause the 2 problems
The combobox disappears when I click on it (unless the whole, grid is set to non editable)
Why the combo has no data
Or am I going about this the wrong way.
Thanks in advance for any help!
It appear that the property names are prefixed with "k-data", and then
the camelcase names converted to the "dash" syntax (similar to what
angular does). IS this just the rules that we follow?
Yes - the documentation is here.
The combobox disappears when I click on it (unless the whole, grid is
set to non editable)
This is because the column is editable, so it gets replaced by the default editor. You can prevent this from happening using the technique I described here. I also used it in the demo.
Why the combo has no data
Your template doesn't work; it should be something like this:
var tmpl = '<input style="width:100%" ' +
'kendo-combo-box ' +
'k-data-text-field="\'display\'" ' +
'k-data-value-field="\'rego\'" ' +
'k-data-source="dataItem.carSource"' +
'k-value="dataItem.car.rego" />';
and for that to work, you need to give each data item a reference to the car data (you can't execute a function there, the template is evaluated against a kendo.data.Model instance).
(updated demo)
I'm using 100% pure javascript, tried Jquery but it didn't help. Code not working in FF/Chrome/Safari.
I have built Edit-In-Place functionality where when the user clicks "Edit" (calling external function with onclick - passing in item_id) -- a string of text is hidden to reveal an input with the same string of text in it. (by changing classes) "Edit" is also replaced by "Save". When done editing the string - the user clicks save, and everything reverts back to normal.
AJAX is processing all the updates - but commenting out the AJAX block does not fix it.
I am loading a stream of these objects. The javascript works for all of them - but only updates the DOM, visually anyway for what appears is items before the last 24 hours. The blocks themselves are identical. That is - items that have been added within the last 18-26 hours when I click "Edit", do nothing. BUT if I alert out the class of the element I want to edit it says "editing" (as opposed to "saved") like it is working. (see below) Although this change is never reflected in inspect element.
Code on Page
<input type="text" class="input_field" id="input_254" value="Foo" onkeydown="javascript: if (event.keyCode == 13) { update(254); }" style="display: none; ">
<span class="user_links" id="display_269" style="display:none;">Foo</span> //hidden span that holds the value and acts at the check
<span id="edit_state_269" class="saved" style="display: none;">Foo</span>
<span onclick="update(269)" id="edit_269">Edit</span>
External Javascript
function update(item_id) {
var links_span = document.getElementById('display_' + item_id);
var input_span = document.getElementById('input_' + item_id);
var string_old = document.getElementById('edit_state_' + item_id).innerHTML;
var state_check = document.getElementById('edit_state_' + item_id);
var edit_button = document.getElementById('edit_' + item_id);
if (state_check.getAttribute('class') == 'saved') {
// Hide the links display list and show the input field
links_span.style.display = 'none';
input_span.style.display = 'inline';
// Change the Edit button text and state_check
edit_button.innerHTML = 'Save';
state_check.setAttribute('class','editing');
//alert(state_check.getAttribute('class')); // this alerts "editing" although in DOM it is still "saved" on the blocks that are the problem
If any more details would be helpful - I will provide them.
It is a devil of a problem - with no obvious solution. Would really appreciate any direction you can give!
Solved. As usual it's the little things. The first few blocks were being loaded on page load - and then hidden as the user navigated resulting in duplicate IDs. Javascript naturally selected the one higher on the page - the one that was hidden.