I am trying to use epiceditor in a basic Ruby on Rails application. I followed the instructions on the webpage http://epiceditor.com/#. The epic editor window is not displaying for some reason...thanks
This is how I set up the code on my edit.html.erb view:
```
<head>
<meta charset="utf-8">
<script src="js/epiceditor.js"></script>
</head>
<body>
<div id="epiceditor"></div>
</body>
<script type="text/javascript">
var editor = new EpicEditor().load();
</script>
<h1>Edit Wiki</h1>
<%= form_for #mywiki do |f| %>
<div>
<%= f.label :title %>
<%= f.text_field :title, :size => 75 %>
</div>
<div>
<%= f.label :body %>
<%= f.text_area :body, :rows => "35", :cols => "75" %>
</div>
<%= f.submit %>
<% end %>
```
is there some valid CSS added?
After adding the css from epic editor's site, I got it working.
Ruby - 2.3.0
Rails - 4.2.5
Bootstrap 3 gem 'bootstrap-sass', '~> 3.3.6'
EpicEditor gem 'epic-editor-rails'
Ran bundle from my app root:
rails_app_root$ bundle
Updated /app/assets/stylesheets/application.scss
#import 'base/epiceditor';
//#import 'preview/bartik';
//#import 'preview/github';
//#import 'preview/preview-dark';
//#import 'editor/epic-dark';
//#import 'editor/epic-light';
// This is shown here for reference just to instruct that I have imported EpicEditor themes first.
#import "bootstrap-sprockets";
#import "bootstrap";
Note I have just imported base/epiceditor and others are commented out. The reason being they were overriding the Bootstrap styling which wasn't needed.
On my form partial /app/views/.../_form.html.haml under <form> element following things I added:
#myTextAreaContainer(style='display: none')
= text_area_tag("body", myText, id: 'myTextArea')
.form-group
= label_tag(nil, "My Text", class: "col-md-3 control-label")
#myTextEpicEditor.col-md-7
On my show view /app/views/..../show.html.haml
.row
.col-md-10
#myDetailsView.form-horizontal
%div(style='display: none')
= text_area_tag("myText", #my_text, id: 'viewMyTextTextArea')
.form-group
= label_tag(nil, "My Text", class: "col-sm-3 control-label")
#viewMyTextBodyEpicEditor.col-sm-6
In /app/assets/javascripts/custom.js
(function ($) {
isEmptyValue = function(value) {
return ( undefined === value || null === value || "" === value || ("" === value.replace(/[\n\r]/g, '')) )
}
myForm = function() {
return $("form#myForm");
};
// Note: EpicEditor requires just the id value not a jquery selector
// like "#myTextEpicEditor"
myFormEpicEditorContainerId = function() {
return "myTextEpicEditor";
}
// Note: EpicEditor requires just the id value not a jquery selector
// like "#myTextArea"
myFormTextAreaId = function() {
return "myTextArea";
}
myFormMyTextLocalStorageName = function() {
return "myTextEpicEditorLocalStorage";
}
myFormMyTextBodyFileName = function() {
return "myTextFile";
}
myFormEpicEditorOpts = function() {
var myTextEpicEditorOpts = {
container: myFormEpicEditorContainerId(),
textarea: myFormTextAreaId(),
localStorageName: myFormMyTextLocalStorageName(),
file: {
name: myFormMyTextBodyFileName(),
defaultContent: '',
autoSave: 100
},
};
return myTextEpicEditorOpts;
}
loadEpicEditorOnMyForm = function() {
var selector = "#" + myFormEpicEditorContainerId();
if ($(selector).length == 0) {
return;
}
var myFormEpicEditorInstance = new EpicEditor(myFormEpicEditorOpts()).load();
};
bindClickEventOnSaveBtnOnMyForm = function() {
var saveBtnObj = $("#saveBtn");
if (saveBtnObj.length == 0) {
return;
}
saveBtnObj.off("click").on("click", function(event) {
var myFormObj = myForm();
var myFormEpicEditorInstance = new EpicEditor(myFormEpicEditorOpts());
// console.log(myFormEpicEditorInstance);
var myText = myFormEpicEditorInstance.exportFile(myFormMyTextBodyFileName(), 'text');
// console.log(myText);
if (isEmptyValue(myText)) {
alert("Please enter text");
event.stopPropagation();
return false;
}
myFormObj.submit();
});
};
// Used for rendering EpicEditor in ONLY preview mode with only
// full screen button and when the epic editor is switched to
// full screen mode it hides the editor pane.
displaySavedMyTextPreview = function() {
var myDetailsView = $("#myDetailsView")
if (myDetailsView.length == 0) {
return;
};
var viewMyTextEpicEditorOpts = {
container: 'viewMyTextBodyEpicEditor',
textarea: 'viewMyTextTextArea',
button: {
preview: false,
edit: false,
fullscreen: true,
bar: "auto"
},
};
var viewMyTextEpicEditorInstance = new EpicEditor(viewMyTextEpicEditorOpts);
viewMyTextEpicEditorInstance.load(function() {
console.log("loaded");
viewMyTextEpicEditorInstance.preview();
});
viewMyTextEpicEditorInstance.on('fullscreenenter', function() {
// console.log("full screen enter");
$(viewMyTextEpicEditorInstance.getElement('editorIframe')).hide();
});
};
}) (jQuery);
var ready;
ready = function() {
loadEpicEditorOnMyForm();
bindClickEventOnSaveBtnOnMyForm();
displaySavedMyTextPreview();
};
$(document).ready(ready);
$(document).on('page:load', ready);
Note
The code shown above is working code. In case it doesn't work for you try looking for any mistakes made in typing the element selectors etc.
I assume that jQuery is available in the application.
Though I have not tried it but you can include multiple EpicEditors on the same page by passing in custom options like I have demonstrated.
Related
I have ruby on rails application with stimulus.js and dropzone.js for uploading attachment. There is now a limit on uploading one file, but this allows you to upload more than one file and just shows an error message on them. I need that it is not possible to upload more than one file and if after that the user tries to upload another one, a replacement occurs.
dropzone_controller.js
import Dropzone from "dropzone";
import { Controller } from "stimulus";
import { DirectUpload } from "#rails/activestorage";
import {
getMetaValue,
toArray,
findElement,
removeElement,
insertAfter
} from "helpers";
export default class extends Controller {
static targets = ["input"];
connect() {
this.dropZone = createDropZone(this);
this.hideFileInput();
this.bindEvents();
Dropzone.autoDiscover = false; // necessary quirk for Dropzone error in console
}
// Private
hideFileInput() {
this.inputTarget.disabled = true;
this.inputTarget.style.display = "none";
}
bindEvents() {
this.dropZone.on("addedfile", file => {
setTimeout(() => {
file.accepted && createDirectUploadController(this, file).start();
}, 500);
});
this.dropZone.on("removedfile", file => {
file.controller && removeElement(file.controller.hiddenInput);
});
this.dropZone.on("canceled", file => {
file.controller && file.controller.xhr.abort();
});
}
get headers() {
return { "X-CSRF-Token": getMetaValue("csrf-token") };
}
get url() {
return this.inputTarget.getAttribute("data-direct-upload-url");
}
get maxFiles() {
return this.data.get("maxFiles") || 1;
}
get maxFileSize() {
return this.data.get("maxFileSize") || 256;
}
get acceptedFiles() {
return this.data.get("acceptedFiles");
}
get addRemoveLinks() {
return this.data.get("addRemoveLinks") || true;
}
}
class DirectUploadController {
constructor(source, file) {
this.directUpload = createDirectUpload(file, source.url, this);
this.source = source;
this.file = file;
}
start() {
this.file.controller = this;
this.hiddenInput = this.createHiddenInput();
this.directUpload.create((error, attributes) => {
if (error) {
removeElement(this.hiddenInput);
this.emitDropzoneError(error);
} else {
this.hiddenInput.value = attributes.signed_id;
this.emitDropzoneSuccess();
}
});
}
createHiddenInput() {
const input = document.createElement("input");
input.type = "hidden";
input.name = this.source.inputTarget.name;
insertAfter(input, this.source.inputTarget);
return input;
}
directUploadWillStoreFileWithXHR(xhr) {
this.bindProgressEvent(xhr);
this.emitDropzoneUploading();
}
bindProgressEvent(xhr) {
this.xhr = xhr;
this.xhr.upload.addEventListener("progress", event =>
this.uploadRequestDidProgress(event)
);
}
uploadRequestDidProgress(event) {
const element = this.source.element;
const progress = (event.loaded / event.total) * 100;
findElement(
this.file.previewTemplate,
".dz-upload"
).style.width = `${progress}%`;
}
emitDropzoneUploading() {
this.file.status = Dropzone.UPLOADING;
this.source.dropZone.emit("processing", this.file);
}
emitDropzoneError(error) {
this.file.status = Dropzone.ERROR;
this.source.dropZone.emit("error", this.file, error);
this.source.dropZone.emit("complete", this.file);
}
emitDropzoneSuccess() {
this.file.status = Dropzone.SUCCESS;
this.source.dropZone.emit("success", this.file);
this.source.dropZone.emit("complete", this.file);
}
}
function createDirectUploadController(source, file) {
return new DirectUploadController(source, file);
}
function createDirectUpload(file, url, controller) {
return new DirectUpload(file, url, controller);
}
function createDropZone(controller) {
return new Dropzone(controller.element, {
url: controller.url,
headers: controller.headers,
maxFiles: controller.maxFiles,
maxFilesize: controller.maxFileSize,
acceptedFiles: controller.acceptedFiles,
addRemoveLinks: controller.addRemoveLinks,
autoQueue: false
});
}
_form.html.erb
<div data-lite-visibility-target="dynamic" class="space-y-8 <%= #automate_task_report.attachment.present? ? '' : "hidden" %>" >
<div class="form-group inverted">
<%= form.label :attachment, "Upload test execution results", class: "form-label" %>
<button type="button" class="dropzone dropzone-default dz-clickable form-control form-file form-file-btn" data-controller="dropzone" data-dropzone-max-file-size="10" data-dropzone-max-files="1" data-dropzone-accepted-files=".xml,.html,.jpg,.jpeg,.png,.gif">
<%= form.file_field :attachment, direct_upload: true, data: { target: 'dropzone.input' } %>
<div class="dropzone-msg dz-message needsclick flex m-0">
<% if #automate_task_report.attachment.attached? %>
<%= form.hidden_field :attachment, value: #automate_task_report.attachment.signed_id %>
<div class="mx-5 attachment vertical">
<%= link_to #automate_task_report.attachment, target: "_blank", class:"attachment-thumb" do %>
<%= image_tag(#automate_task_report.attachment) %>
<% end %>
<%= link_to #automate_task_report.attachment.filename.to_s, #automate_task_report.attachment, target: "_blank", class:"attachment-name" %>
<%= link_to #automate_task_report.attachment, download: #automate_task_report.attachment, class:"btn btn-primary attachment-btn" do %>
<span class="icon text-icon-default icon-download"></span>
<% end %>
</div>
<% end %>
<span class="icon text-icon-lg icon-file-image-plus-lg mr-3"></span>
<div class="text-left mt-0">
<p>Upload a file or drag and drop</p>
<p class="text-xs">XML, HTML, PNG, JPG, GIF up to 10MB</p>
</div>
</div>
</button>
</div>
</div>
</div>
</div>
I'm already tried things like
= {
maxFiles: 1
};
but it didn't work for me. How can I achive expected result?
Use the "accept" callback to check for the max amount of files:
accept: function(file, done) {
if (/* do a check here */) {
done(error message);
// File is cancel.
file.status = Dropzone.CANCELED;
}
}
key point here is change the file status to cancelled to let dropzone know about it
I am trying to render the google map api on my web page but I keep getting this error:
edit:1076 Uncaught ReferenceError: $ is not defined
Here is the process. The call that leads to the rendering of the api map begins in an edit.html.haml file.
edit.html.haml
........html.....
........html.....
........html.....
.col-md-5.col-md-push-1
.form-group
%label.control-label!= t('.city_and_country')
.controls
%p.enabled
%input.form-control#location_scratch{ type: :text, autocomplete: :off, value: #account.location }
%a{ href: 'javascript:EditMap.clearLocation();' }= t('clear')
%p.error#not_found{ style: 'display:none' }
= t('.location_error_message')
#map
#-----THIS IS WHERE IT STARTS-------------
!= map_init('map,' #account.latitude ? 7 : 0)
#-------------------------------------------
= f.hidden_field :location
= f.hidden_field :country_code
= f.hidden_field :latitude
= f.hidden_field :longitude
From map_init in the edit.html.haml file we then travel to the map_helper.rb file
map_helper.rb
module MapHelper
def map_init(id, zoom = 2)
map_script_load + map_js_initialization(id, zoom)
end
private
def map_script_load
key = Rails.application.config.google_maps_api_key
uri = "#{request.ssl? ? 'https' : 'http'}://maps.googleapis.com/maps/api/js?v=3&key=#{key}"
"<script async defer src='#{uri}' type='text/javascript'></script>"
end
def map_js_initialization(id, zoom)
javascript_tag <<-JSCRIPT
$(document).on('page:change', function() {
Map.load('#{id}', 25, 12, 2);
Map.moveTo(25, 12, #{zoom});
});
JSCRIPT
end
and then from there it goes to a Map.js file that calls the load and moveTo methods.
The problem though is in the map_js_initialization method. The JSCRIPT javascript code fails for $(document).on('page:change') Why is it failing for the jQuery object? I saw that an error like this occurs because the google maps api loads before the jquery source code. However, I've also placed an async defer on the maps and still no change in result.
I have require jquery in my application.js file and then in my application.html.haml file I have
!!!5
%html
%head
- page_title = content_for?(:html_title) ? "#{yield(:html_title)}" : t('.openhub')
%title= page_title
%meta{ name: 'viewport', content: 'width=device-width, initial-scale=1.0' }
%meta{ name: 'description', content: page_context[:description] }
%meta{ name: 'keywords', content: page_context[:keywords] }
%meta{ name: 'digits-consumer-key', content: ENV['DIGITS_CONSUMER_KEY'] }
%meta{ name: 'google-site-verification', content: 'jKkWeVQ0tB1bffJYg7xXAtcIM-nrjjVxhP3ohb8UH2A' }
= yield :custom_head
= stylesheet_link_tag 'application', media: 'all'
= csrf_meta_tags
%body{ zoom: 1 }
= yield :session_projects_banner
.container#page
%header= render partial: 'layouts/partials/header'
= render partial: 'layouts/partials/page'
.clear
%footer= render partial: 'layouts/partials/footer'
= yield(:javascript) if content_for?(:javascript)
= javascript_include_tag 'application',
'https://www.google.com/recaptcha/api.js',
'https://cdn.digits.com/1/sdk.js',
'//cdn.optimizely.com/js/3568250046.js',
cache: 'cached_js_files',
async: true
- if Rails.env.production?
<script type="text/javascript">
(function() {
var hm = document.createElement('script'); hm.type ='text/javascript'; hm.async = true;
hm.src = ('++u-heatmap-it+log-js').replace(/[+]/g,'/').replace(/-/g,'.');
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(hm, s);
})();
</script>
So in conclusion. I should be able to render the maps because I have at the top of my <head> tag application.js which has jquery and then the call to the api comes afterwards. Am I missing something here?
I found a nifty property to solve the problem that I was having. While the problem that jquery was not loading in the correct order, the map still was not rendering for some reason. This little snippet of code solved my problem.
document.onreadystatechange = function () {
if (document.readyState == "complete") {
Map.load('#{id}', 25, 12, 2);
Map.moveTo(25, 12, #{zoom});
}
};
I am trying to dynamically change the markers in Gmaps4Rails based on a user search form. I am using the code below.
The problem I am encountering is that the call to Gmaps.map.replaceMarkers within $('#locations_search').submit gives an error: Gmaps.map is undefined.
I checked using the javascript debbuger, and indeed once I enter the submit function (I have a breakpoint there), Gmaps.map is undefined. When stopping with a breakpoint in the first lines of Gmaps.map.callback, the object Gmaps.map is defined.
Probably I am missing something. It seems to be some variable scope problem here?
Gmaps.map.callback = function() {
var firstMarker = Gmaps.map.markers[0];
var map = Gmaps.map.map;
firstMarker.infowindow.open(map, firstMarker.serviceObject);
$('#locations_search').submit(function () {
var url = '/locations.json/?' + $(this).serialize();
$.getJSON(url, function(data){
Gmaps.map.replaceMarkers(data);
});
$.get(this.action, $(this).serialize(), null, 'script');
return false;
});
}
Thanks a lot!
It's just a question of script order.
Gmaps.map is created in your view when
You call either gmaps or gmaps4rails helpers.
Solution:
Add your scripts after your call to the gem's helpers
wrap your js code in <% content_for :scripts do %> code <% end %>
in your view:
<%= gmaps(...) %>
<% content_for :scripts do %>
<script type="text/javascript">
Gmaps.map.callback = function() { ... }
</script>
<% end %>
Gmaps.map.callback = function() {
var namespace = Gmaps.map;
var firstMarker = namespace.markers[0];
var map = namespace.map;
firstMarker.infowindow.open(map, firstMarker.serviceObject);
$('#locations_search').submit(function () {
var url = '/locations.json/?' + $(this).serialize();
$.getJSON(url, function(data){
namespace.replaceMarkers(data);
});
$.get(this.action, $(this).serialize(), null, 'script');
return false;
});
}
I would like to add a marker with link in marker, so, when i click that marker, i will redirect to another page
Is anybody know to do it?
Thanks
I already added my code like this :
In controller:
#json = #businesses.results.to_gmaps4rails do |object|
"\"link\": \"#{root_url}\""
end
In view :
<%= gmaps4rails(#json) %>
<% content_for :scripts do %>
<script type="text/javascript">
function redirect_to(url) {
window.location = url;
};
Gmaps4Rails.callback = function() {
function say_yo(arg) { return function(){alert('yo '+ arg + '!' );};};
for (var i = 0; i < Gmaps4Rails.markers.length; ++i) {
google.maps.event.addListener(Gmaps4Rails.markers[i].google_object, 'click', redirect_to(Gmaps4Rails.markers[i].link));
}
}
</script>
<% end %>
Is it any wrong? because there just an info window that show after i clicked the marker(Not redirect to any page)
First include the link inside the json:
Model.all.to_gmaps4rails do |object|
"\"link\": \"your link as string\""
end
Then add the extra listeners in your view (beware to include this AFTER your call to the gmaps method):
<%= gmaps(whatever you need here) %>
<% content_for :scripts do %>
<script type="text/javascript">
function redirect_to(url) {
window.location = url;
};
Gmaps4Rails.callback = function() {
function say_yo(arg) { return function(){alert('yo '+ arg + '!' );};};
for (var i = 0; i < Gmaps4Rails.markers.length; ++i) {
google.maps.event.addListener(Gmaps4Rails.markers[i].google_object, 'click', redirect_to(Gmaps4Rails.markers[i].link));
}
}
</script>
<% end %>
I'm trying to build a chained select menu. This is the tutorial I used: http://railscasts.com/episodes/88-dynamic-select-menus
Something goes wrong, I don't know what I'm missing.
javascript
var sottocategorie = new Array();
<% for element in #sottocategorie -%>
sottocategorie.push(new Array(<%= element.idcategoria %>, <%= element.c2 %>));
<% end -%>
function selezionacategoria() {
categoriaid = $('segnalazione_categoria1').getValue();
options = $('segnalazione_categoria2').options;
options.length = 1;
sottocategorie.each(function(elementement) {
if (element[0] == categoriaid) {
options[options.length] = new Option(element[1]);
}
});
if (option.length == 1) {
$('sottocategoria_field').hide();
} else {
$('sottocategoria_field').show();
}
}
document.observe('dom:loaded', function() {
//selezionacategoria();
$('segnalazione_categoria1').observe('change', selezionacategoria);
});
html
<label for="segnalazione_categoria1">Categoria:</label>
<%= f.collection_select :categoria1, Categorium.find(:all), :id, :c1, :prompt => "Seleziona categoria" %>
<p id="sottocategoria_field">
<label for="segnalazione_categoria2">Categoria:</label>
<%= f.collection_select :categoria2, Sottocategoria1.find(:all), :id, :c2, :prompt => "Seleziona sottocategoria" %>
</p>
routes:
match '/:controller(/:action(/:id))'
The chained select menu doesn't run, the "filter" doesn't work, and also
if (option.length == 1) {
$('sottocategoria_field').hide();
} else {
$('sottocategoria_field').show();
}
doesn't work.
SOLUTION:
I've changed the javascript file using jQuery:
var sottocategorie = new Array();
<% for sottocategoria in #sottocategorie %>
sottocategorie.push(new Array('<%= sottocategoria.idcategoria %>', '<%= escape_javascript(sottocategoria.c2) %>', <%= sottocategoria.id %>));
<% end %>
function menuSelected(orig_menu, new_menu, item_array) {
orig_value = $('#segnalazione_categoria1 :selected').text();
//alert(orig_value);
$(new_menu).empty();
jQuery.each(item_array, function(i, val) {
if (val[0] == orig_value) {
$(new_menu).append($("<option></option>").attr("value",val[2]).text(val[1]));
}
});
}
$(document).ready(function(){
//bind the click event to the city submit button
$('#submit_button').bind('click', function () {
menuSelected('#segnalazione_categoria1', '#segnalazione_categoria2', sottocategorie);
});
});
SOLUTION WITH THREE LEVELS OF CHAINING:
var sottocategorie = new Array();
var subsottocategorie = new Array();
<% for sottocategoria in #sottocategorie %>
sottocategorie.push(new Array('<%= sottocategoria.idcategoria %>', '<%= escape_javascript(sottocategoria.c2) %>', <%= sottocategoria.id %>));
<% end %>
<% for subsottocategoria in #subsottocategorie %>
subsottocategorie.push(new Array('<%= subsottocategoria.idsottocategoria1s %>', '<%= escape_javascript(subsottocategoria.c3) %>', <%= subsottocategoria.id %>));
<% end %>
function menuSelected(orig_menu, new_menu, item_array) {
orig_value = $(''+ orig_menu + ' :selected').text();
//alert(orig_value);
$(new_menu).empty();
jQuery.each(item_array, function(i, val) {
if (val[0] == orig_value) {
$(new_menu).append($("<option></option>").attr("value",val[1]).text(val[1]));
}
});
}
$(document).ready(function(){
$(".nascosto").hide();
$(".nascosto1").hide();
//bind the click event to the city submit button
$('#segnalazione_categoria1').bind('click', function () {
$(".nascosto").show();
$('#segnalazione_categoria3').empty();
menuSelected('#segnalazione_categoria1', '#segnalazione_categoria2', sottocategorie);
});
$('#segnalazione_categoria2').bind('click', function (event) {
$(".nascosto1").show();
menuSelected('#segnalazione_categoria2', '#segnalazione_categoria3', subsottocategorie);
});
});