I am creating an app called Employee Management System using Rails 7. To add an employee, I have created a form. Here I have used nested-form-fields gem for adding contacts of employee. The problem is when the form is loaded the first time, when I want to add or remove the contact field, it redirects to the same form. But when I refresh the page, it starts working without any issue. Even fields added or removed get reflected in the database. It only creates this issue when create or update employee page is loaded first time.
What I have found is when I click the link to add employee and the form opens. The events are not loaded in html. As we can see in below image near body tag:
But after refresh, events are loaded:
I have seen js.coffee file in gem folder but that is too advanced for a beginner like me. The code there looks like this:
window.nested_form_fields or= {}
nested_form_fields.bind_nested_forms_links = () ->
$('body').off("click", '.add_nested_fields_link')
$('body').on 'click', '.add_nested_fields_link', (event, additional_data) ->
$link = $(this)
object_class = $link.data('object-class')
association_path = $link.data('association-path')
added_index = $(".nested_#{association_path}").length
$.event.trigger("fields_adding.nested_form_fields",{object_class: object_class, added_index: added_index, association_path: association_path, additional_data: additional_data});
if $link.data('scope')
$template = $("#{$link.data('scope')} ##{association_path}_template")
else
$template = $("##{association_path}_template")
target = $link.data('insert-into')
template_html = $template.html()
# insert association indexes
index_placeholder = "__#{association_path}_index__"
template_html = template_html.replace(new RegExp(index_placeholder,"g"), added_index)
# look for replacements in user defined code and substitute with the index
template_html = template_html.replace(new RegExp("__nested_field_for_replace_with_index__","g"), added_index)
# replace child template div tags with script tags to avoid form submission of templates
$parsed_template = $(template_html)
$child_templates = $parsed_template.closestChild('.form_template')
$child_templates.each () ->
$child = $(this)
$child.replaceWith($("<script id='#{$child.attr('id')}' type='text/html' />").html($child.html()))
if target?
$('#' + target).append($parsed_template)
else
$template.before( $parsed_template )
$parsed_template.trigger("fields_added.nested_form_fields", {object_class: object_class, added_index: added_index, association_path: association_path, event: event, additional_data: additional_data});
false
$('body').off("click", '.remove_nested_fields_link')
$('body').on 'click', '.remove_nested_fields_link', ->
$link = $(this)
return false unless $.rails == undefined || $.rails.allowAction($link)
return false if $link.attr('disabled')
object_class = $link.data('object-class')
delete_association_field_name = $link.data('delete-association-field-name')
removed_index = parseInt(delete_association_field_name.match('(\\d+\\]\\[_destroy])')[0].match('\\d+')[0])
$.event.trigger("fields_removing.nested_form_fields",{object_class: object_class, delete_association_field_name: delete_association_field_name, removed_index: removed_index });
$nested_fields_container = $link.parents(".nested_fields").first()
delete_field = $nested_fields_container.find("input[type='hidden'][name='#{delete_association_field_name}']")
if delete_field.length > 0
delete_field.val('1')
else
$nested_fields_container.before "<input type='hidden' name='#{delete_association_field_name}' value='1' />"
$nested_fields_container.hide()
$nested_fields_container.find('input[required]:hidden, select[required]:hidden, textarea[required]:hidden').removeAttr('required')
$nested_fields_container.trigger("fields_removed.nested_form_fields",{object_class: object_class, delete_association_field_name: delete_association_field_name, removed_index: removed_index});
false
$(document).on "page:change turbolinks:load", ->
nested_form_fields.bind_nested_forms_links()
jQuery ->
nested_form_fields.bind_nested_forms_links()
#
# * jquery.closestchild 0.1.1
# *
# * Author: Andrey Mikhaylov aka lolmaus
# * Email: lolmaus#gmail.com
# *
#
$.fn.closestChild = (selector) ->
$children = undefined
$results = undefined
$children = #children()
return $() if $children.length is 0
$results = $children.filter(selector)
if $results.length > 0
$results
else
$children.closestChild selector
Is there anything that can be changed to resolve this issue? Please help!!
After working ahead on my app for 2 days, same issue came when I added jquery to application.js for adding a checkbox that makes Permanent address and local address same. So I came to conclusion that the issue is not because of gem but some other reason. So I googled and found that turbolink is the main culprit. After searching in turbo-rails github repository found the issue in 'closed' category. By adding a simple line to my head section in application.html.erb, got read of the problem. Line is:
<meta name="turbo-visit-control" content="reload">
Though, there is a problem. After adding this line, flash messages disappeared because of reload. So I googled again to find out a a better way. Removed this line, and in application.js, changed
import "#hotwired/turbo-rails"
to
import { Turbo } from "#hotwired/turbo-rails"
Turbo.session.drive = false`
This I found in turbo-rails readme.md.
And flash messages reappeared with no need to refresh the page.
Related
I have a set of scripts that I'm using that interact with each other. I use a client, user event and suitelet script to create a button that, when pressed, opens a popup with a list of items filtered by vendor.
It works fine when I'm in edit however when I use it while creating a record problems arise. Since the record to be created has no vendor or id I can't retrieve an item by vendor. What I'm trying to do is to have the Suitelet retrieve the info from the vendor field that is entered prior to it being saved. Therefore I can filter all the items by vendor and add the necessary items in one go. Is this possible? Am I able to access the info before it is submitted.
Below are the Client and Suitelet. The User Event is just a call to the suitelet so for the sake of brevity I left it out.
Client Script
function addItemButtonCallback(data){
nlapiSelectNewLineItem('item');
nlapiSetCurrentLineItemValue('item', 'item', data);
nlapiCommitLineItem('inventoryitem');
}
function addItemButton() {
var id = nlapiGetFieldValue('id');
if (id != "") {
var url = nlapiResolveURL('SUITELET', 'customscript_val', 'customdeploy1') + '&poId='+id;
window.open(url, '_blank', 'width=500,height=500');
}
}
Suitelet
function suitelet(request, response){
if(request.getMethod() == 'GET') {
var form = nlapiCreateForm('Add Item');
form.addSubmitButton('Submit');
var itemfield = form.addField('custpage_val', 'select', 'Item');
var id = request.getParameter('id');
var rec = nlapiLoadRecord('purchaseorder', id);
var vend = rec.getFieldValue('entity');
var search = nlapiSearchRecord(...search parameters...);
for (result in search){
if (search[result].getValue('vendor') == vend){
itemfield.addSelectOption(search[result].id, nlapiLookupField('inventoryitem', search[result].id, 'itemid'));
}
}
response.writePage(form);
} else {
var data = request.getParameter('custpage_item');
response.write('<html><body><script>window.opener.addItemButtonCallback("'+data+'"); window.close();</script></body></html>');
}
}
Use nlapiGetFieldValue('entity') on the clientscript and pass it to the Suitelet using a query parameter just like you are doing with poId (if you do this you might not even need poId after all + no need to load the record on the suitelet).
Also, you might want to optimize your code by running one search passing an array of itemids instead of calling nlapiLookupField for each item.
You might need to modify your beforeLoad so the entity is inserted dynamically when the button is pressed (I cant remember if clientscript button does this) . Something like this:
var suiteletURL = nlapiResolveURL('SUITELET', 'customscript_val', 'customdeploy1');
var script = "var entity = nlapiGetFieldValue('entity'); var url = '" + suiteletURL + "'&entityId=' + entity;window.open(url, '_blank', 'width=500,height=500')";
var button = form.addButton('custpage_addItemButton', 'Add Item', script);
I've been writing a script that lets the user press a button three times, once the three times are up the button is to be greyed out and unclickable. I've made it so that the button cannot be clicked (using VBScript) but I want to use Javascript to also grey out at that point.
What I want to do is have javascript search for a file on the users local drive "C:/sys/" and if that file exists then grey out the button.
The issue that I having is that the only part of the file name that I know will be correct is the last two characters "-3" the beginning of the file name could be any amount of alphanumeric characters. I thought I could use a regex to find the file but I have no idea how to get the script to search for the file using the directory path + the regex. Example for the code:
function checkfile()
{
var fileLocation = "C:/sys/"
var regex = new RegExp('\w+\-3');
if(fso.FileExists(fileLocation + regex)) {
alert("File found successfully");
} else {
alert("File not found.");
}
}
window.onload = checkfile;
I thought that might work but thinking about it makes me wonder if that script is actually searching for a file called "C:/sys/\w+-3" literally rather than using the regex.
Bare in mind that this is part of a much larger script so "fso" is already defined using Set fso = CreateObject("Scripting.FileSystemObject") in VBScript.
The whole code can be found here
<script language="VBScript">
Set objShell = CreateObject("WScript.Shell")
Set fso = CreateObject("Scripting.FileSystemObject")
System = "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\System\"
DisableTaskMgr
dim currentDir : currentDir = objShell.CurrentDirectory 'Finds the scripts current directory'
'TO DO: Check whether UserName method below can be used over network'
UserName = objShell.ExpandEnvironmentStrings("%USERNAME%") 'Grabs the username of the current user'
pathToConfig = currentDir & "/config.ini"
pathToKeyFile = "C:/sys/"
'Gets the current version number from config file'
If fso.FileExists(pathToConfig) Then 'Makes sure that the config file exists'
Set ConfigFile = fso.GetFile(pathToConfig) 'Config file contains only the version number'
If ConfigFile.Size > 0 Then 'This makes sure that there is a version number set inside the config file'
Set versionFile = fso.OpenTextFile(pathToConfig, 1)
Version = versionFile.ReadAll
Else
EnableTaskMgr
window.close 'If config file cannot be reached then the script will end prematurely and will be shown next time the user logs in once the config file can be reached.'
End If
Else
window.close
End If
On Error Resume Next
versionFile.close
'Searches C:/sys/ for keyfile'
If fso.FileExists(pathToKeyFile & Version) Then
EnableTaskMgr
window.close
Else
End If
sub Postpone
ChkOne = pathToKeyFile & Version & "-1"
ChkTwo = pathToKeyFile & Version & "-2"
ChkThree = pathToKeyFile & Version & "-3"
If fso.FileExists(ChkThree) Then
'Placeholder code'
MsgBox "Maximum number of Postpone attempts used. You must now choose to either Accept or Decline the Policy."
ElseIf fso.FileExists(ChkTwo) Then
'Delete file ChkTwo - Create file ChkThree'
fso.DeleteFile(ChkTwo)
Set objFile = fso.CreateTextFile(ChkThree)
window.close
ElseIf fso.FileExists(ChkOne) Then
'Delete file ChkOne - Create file ChkTwo'
fso.DeleteFile(ChkOne)
Set objFile = fso.CreateTextFile(ChkTwo)
window.close
Else
'Create file ChkOne'
Set objFile = fso.CreateTextFile(ChkOne)
window.close
End If
end sub
sub DisableTaskMgr
objShell.RegWrite System, "REG_SZ"
objShell.RegWrite System & "/DisableTaskMgr", 1, "REG_DWORD"
end sub
sub EnableTaskMgr
objShell.RegWrite System, "REG_SZ"
objShell.RegWrite System & "/DisableTaskMgr", 0, "REG_DWORD"
end sub
sub Logon 'Method only runs when user clicks "I Accept"'
On Error Resume Next
Call EnableTaskMgr
If Not (fso.FileExists(currentDir & "/store/" & LCase(UserName) & ".csv")) Then
On Error Resume Next
Set objFile = fso.CreateTextFile(currentDir & "/store/" & LCase(UserName) & ".csv", True)
objFile.close
Else
End If
Set objCSVFile = fso.OpenTextFile(currentDir & "/store/" & UserName & ".csv", 2, True)
objCSVFile.Write(UserName & "," & Now & "," & Version)
objCSVFile.Close
On Error Resume Next
fso.CreateTextFile("C:/sys/" & Version)
window.close
end sub
sub Logoff 'Method only runs when user clicks "I Decline"'
objShell.Run "shutdown /l"
end sub
</script>
<script type="text/javascript">
function checkfile()
{
var fileLocation = "C:/sys/"
var regex = new RegExp('\w+\-3');
if(fso.FileExists(fileLocation + regex)) {
alert("File found successfully");
} else {
alert("File not found.");
}
}
window.onload = checkfile;
</script>
looking to override the default location of the "print report" button in storyline - by default it directs to report.html a canned page that can retrieve the score and test results variables from the completed test - I would like to have the same results sent to report.php so I can work with results on the server side. story.js file looks like:
function onBWEventInterval()
...
case "BW_PrintResults":
g_oPrintOptions.bShowUserScore = (arrArgs[0] == "true");
g_oPrintOptions.bShowPassingScore = (arrArgs[1] == "true");
g_oPrintOptions.bShowShowPassFail = (arrArgs[2] == "true");
g_oPrintOptions.bShowQuizReview = (arrArgs[3] == "true");
g_oPrintOptions.strName = arrArgs[4];
g_oPrintOptions.strMainQuizId = arrArgs[5];
g_oPrintOptions.arrQuizzes = arrArgs[6].split(",");
window.open(GetBasePath() + g_strContentFolder + "/report.html", "Reports")
break;
If I change the /report.html to /report.php this resolves the issue, however I want to leave the story.js file alone as its re-created with every project change.
there is a user.js file that loads after the story.js and is empty, I'm not sure how to override the function / location in the story.js file.
I have a page in one of my ASP.NET applications (VB) that relies on a 3rd party service being live, and I'm using jQuery to display an overlay if this service is found to be down. I've been using the same approach for other warnings in the app - the difference with this one is that it needs to redirect after the user clicks the button on the warning popup to remove the overlay. For some reason I'm getting the error "Error: The value of the property 'warn_redirect' is null or undefined, not a Function object."
Any assistance is greatly appreciated! My code follows:
jQuery
function warn_redirect(msg, title, nextpage) {
// show modal div
//alert(obj.id);
$("html").css("overflow", "hidden");
$("body").append("<div id='popup_overlay'></div><div id='popup_window'></div>");
$("#popup_overlay").addClass("popup_overlayBG");
$("#popup_overlay").fadeIn("slow");
// build warning box
$("#popup_window").append("<h1>" + title + "</h1>");
$("#popup_window").append("<p id='popup_message'><center>" + msg + "</center></p>");
$("#popup_window").append("<div class='buttons'><center><button id='continue' class='positive' type='submit'>OK</button></center></div>");
// attach action to button
$("#continue").click(popup_remove_redirect(nextpage));
// display warning window
popup_position(400, 300);
$("#popup_window").css({ display: "block" }); //for safari using css instead of show
$("#continue").focus();
$("#continue").blur();
}
function popup_remove_redirect(nextpage) {
$("#popup_window").fadeOut("fast", function () { $('#popup_window,#popup_overlay').trigger("unload").unbind().remove(); });
$("body", "html").css({ height: "auto", width: "auto" });
$("html").css("overflow", "");
window.location.href = nextpage;
}
Here is the VB.NET calling code:
If Status = "DOWN" Then
Dim clsUtility As New Utility
clsUtility.Emailer("email#company.com", "email#company.com", "", "", "The Service Is Down!", "Please investigate")
Dim ScriptString As String = "<script language='javascript'>"
ScriptString += "warn_redirect('Some warning message.', 'Warning', 'AnotherPage.aspx');"
ScriptString += "</script>"
ClientScript.RegisterStartupScript(Me.GetType, "warnscript", ScriptString)
'Response.Redirect("AnotherPage.aspx?ID=" & Session("SelKey") & "&UN=" & Header1.UserNumber) //this didn't work either
End If
Try this,
If Status = "DOWN" Then
Dim clsUtility As New Utility
clsUtility.Emailer("email#company.com", "email#company.com", "", "", "The Service Is Down!", "Please investigate")
Dim ScriptString As String = "<script language='javascript'>"
ScriptString += "window.onload = function(){warn_redirect('Some warning message.', 'Warning', 'AnotherPage.aspx');};"
ScriptString += "</script>"
ClientScript.RegisterStartupScript(Me.GetType, "warnscript", ScriptString)
'Response.Redirect("AnotherPage.aspx?ID=" & Session("SelKey") & "&UN=" & Header1.UserNumber) //this didn't work either
End If
As JQuery is already included, you can also use the below statement instead.
ScriptString += "$(function(){warn_redirect('Some warning message.', 'Warning', 'AnotherPage.aspx');});"
This is because the definition of your script is loaded after the StartupScript is invoked.
You have to include your .js file before the StartupScripts in your page. Look at the "source code" in your browser, and you'll see a bunch of script tags like this:
<script src="/ScriptResource.axd?d=eoXLgy ... 847d" type="text/javascript"></script>
These tags are the scripts inserted as StartUp scripts. Make sure your script is inserted before this in your .aspx (or .master). Perhaps you'll have to include them in the head tag.
By the way, creating a global function, like you warn_redirect, it's not a good practice. You should, at least, namespace it like this:
window.MyFunctions = {
warn_redirect = function (msg, title, nextpage) { /* your code */ }
}
And call it like this: MyFunctions.warn_redirect.
In this way, you have it better organized, and avoid possible collisions with other functions with the same name.
I can't work out how to grab the edited data from a CKEditor instance and post it to a url.
I'm looking at something this:
http://nightly.ckeditor.com/3995/samples/inlineall.html
and I can't work out how the changes can be saved. Can I post the newly edited data to be posted to a PHP along with the ID of the element being edited?
Similarly to this:
editor.on('configLoaded', function(){
// do some stuff
});
I was hoping I could do something like this:
editor.on('clickAway', function(e){
id = e.id();
// do some ajax stuff
});
But I can't seem to find anything, anywhere.
Has anyone worked out how to do this?
Thank you.
I'm sure there are many ways to pull this off, but here's my solution. I'm using the Smarty Template Engine, but this technique should work with vanilla HTML too.
First off, here's an example of some HTML stored in my template file named "dog_fleas.tpl":
<script type="text/javascript" src="/js/ckeditor/ckeditor.js"></script>
<script type="text/javascript" src="/js/admin/mycms.js"></script>
<div>
<div id="flea-blurb" tpl="/templates/dog_fleas.tpl" contenteditable="true">
<h1>My Dog Has Fleas</h1>
<p>This text is editable via the CMS!</p>
</div>
<p>This text is not editable</p>
</div>
The javascript (mycms.js) to handle the inline editing is:
$(document).ready(function() {
CKEDITOR.disableAutoInline = true;
$("div[contenteditable='true']" ).each(function( index ) {
var content_id = $(this).attr('id');
var tpl = $(this).attr('tpl');
CKEDITOR.inline( content_id, {
on: {
blur: function( event ) {
var data = event.editor.getData();
var request = jQuery.ajax({
url: "/admin/cms-pages/inline-update",
type: "POST",
data: {
content : data,
content_id : content_id,
tpl : tpl
},
dataType: "html"
});
}
}
} );
});
});
The above code does a few things:
It converts any div tag with the attribute contenteditable = "true" to inline-editable.
After content is edited (on blur), the editable element id, tpl filename, and edited content are sent to the server via an ajax call.
The tpl attribute is necessary in my situation to identify the file being edited. The element id specifies which element was modified.
Although my example only contains one editable region, this code supports multiple editable regions in a single file.
On the server-side, here's my PHP code. I'm using a framework, so my $this->_POST() functions might look a little unusual, but hopefully you get the idea:
// Get the posted parameters
$new_content = $this->_POST('content');
$content_id = $this->_POST('content_id');
$tpl_filename = $this->_POST('tpl');
// Get the contents of the .tpl file to edit
$file_contents = file_get_contents(APPPATH . 'views' . $tpl_filename);
// create revision as a backup in case of emergency
$revised_filename = str_replace('/', '.', $tpl_filename);
$revised_filename = ltrim ($revised_filename, '.');
file_put_contents(APPPATH . 'views/templates/revisions/' . $revised_filename . '.' . time(), $file_contents);
// Prepare to match the DIV tag
// Credit to: http://stackoverflow.com/questions/5355452/using-a-regular-expression-to-match-a-div-block-having-a-specific-id
$re = '% # Match a DIV element having id="content".
<div\b # Start of outer DIV start tag.
[^>]*? # Lazily match up to id attrib.
\bid\s*+=\s*+ # id attribute name and =
([\'"]?+) # $1: Optional quote delimiter.
\b' . $content_id . '\b # specific ID to be matched.
(?(1)\1) # If open quote, match same closing quote
[^>]*+> # remaining outer DIV start tag.
( # $2: DIV contents. (may be called recursively!)
(?: # Non-capture group for DIV contents alternatives.
# DIV contents option 1: All non-DIV, non-comment stuff...
[^<]++ # One or more non-tag, non-comment characters.
# DIV contents option 2: Start of a non-DIV tag...
| < # Match a "<", but only if it
(?! # is not the beginning of either
/?div\b # a DIV start or end tag,
| !-- # or an HTML comment.
) # Ok, that < was not a DIV or comment.
# DIV contents Option 3: an HTML comment.
| <!--.*?--> # A non-SGML compliant HTML comment.
# DIV contents Option 4: a nested DIV element!
| <div\b[^>]*+> # Inner DIV element start tag.
(?2) # Recurse group 2 as a nested subroutine.
</div\s*> # Inner DIV element end tag.
)*+ # Zero or more of these contents alternatives.
) # End 2$: DIV contents.
</div\s*> # Outer DIV end tag.
%isx';
if (preg_match($re, $file_contents, $matches))
{
$content_to_replace = $matches[0];
$replacement_content = $content_to_replace;
// Replace the inner content of $replacement_content with $new_content
$replacement_content = preg_replace('/(<div(?:.*?)>)(?:.*)(<\/div>)/msi',"$1" . $new_content . "$2", $replacement_content);
// Now replace the content_to_replace with $replacement content in the HTML
$new_file_contents = str_replace($content_to_replace, $replacement_content, $file_contents);
// write out the new .tpl file
file_put_contents(APPPATH . 'views' . $tpl_filename, $new_file_contents);
}
The PHP code above is basically loading the HTML, locating the div tag with the proper id, then replacing the contents of that div tag with the content sent down via the ajax call. The HTML is then re-saved to the server. I also include some code to store backup revisions just in case things go terribly wrong.
I realize that regular expressions aren't always the best solution. In my case, it was difficult to use the PHP Dom Object Model because my HTML content isn't valid HTML. You might look into using the Dom Object Model instead if your system is simpler than mine.
I hope this helps!
I've found the following solution: How do I save inline editor contents on the server?
I'm using the blur event
Using above answer of #clone45 and modified it. The data will be saved busing Save button and only after some changes carried out were old and new data is compared.
Overridden existing save button of inline editor and included below only alerted part of #clone45's answer.
<script>
CKEDITOR.disableAutoInline = true;
$("div[contenteditable='true']").each(function(index) {
var content_id = $(this).attr('id');
var tpl = $(this).attr('tpl');
var oldData = null;
CKEDITOR.inline(content_id, {
on: {
instanceReady: function(event) {
//get current data and save in variable
oldData = event.editor.getData();
// overwrite the default save function
event.editor.addCommand("save", {
modes: {
wysiwyg: 1,
source: 1
},
exec: function() {
var data = event.editor.getData();
//check if any changes has been carried out
if (oldData !== data) {
oldData = data;
$.ajax({
type: 'POST',
url: 'process.php',
data: {
content: data,
content_id: content_id,
tpl: tpl
}
})
.done(function(data) {
alert('saved');
})
.fail(function() {
alert('something went wrong');
});
} else
alert('looks like nothing has been changed');
}
});
}
}
});
});
</script>
Hope this helps!!