I have the following minify function:
public static function minify($buffer)
{
$search = array(
'/\>[^\S ]+/s',
'/[^\S ]+\</s',
'/(\s)+/s'
);
$replace = array(
'>',
'<',
'\\1'
);
$buffer = preg_replace($search, $replace, $buffer);
return $buffer;
}
This function is used with ob_start() in order to minify my html output. The function is taken from here: https://stackoverflow.com/a/6225706/4601581
This function seems to destroy some of my JS. Example: A button i click to execute some JS:
<button type="button" class="btn btn-<?= Config::$ELEMENT_COLOR ?> btn-block" onclick="addLastInput(<?= $mask->getId() ?>)">Do it</button>
The $id i give to addLastInput is simply a number, taken from $mask->getId().
Also i have this little JS code to populate some data array:
<script>lastSubmissionData[<?=$mask->getId()?>] = '<?=json_encode($lastSubmissionData)?>';</script>
And finally my JS function i actually want to execute with my given param (the id) and on the data array:
function addLastInput(maskId) {
var data = lastSubmissionData[maskId];
for (var i = 0; i < data.length; i++) {
// TODO
}
}
Whenever i only load the page, it simply tells me Uncaught SyntaxError: Unexpected end of input in the Chrome console. Whenever i do so with not using the minify function and outcommenting the ob_start() completely, it just works and i can use it.
Conclusion: The minify function seems to brake my inline JS as the comments on the linked SO answer suggest. Question: How can i fix that? I even googled for other minify solutions like this one: https://gist.github.com/tovic/d7b310dea3b33e4732c0
All of them seem to not work by just breaking my site. What's the best solution here to simply let me use my JS code and still minify my html output?
Don't use this minify() function. It's broken.
This function ends up removing some semantically meaningful newlines in inline Javascript code, such as ones appearing at the end of a single-line comment. (For example, it converts your example Javascript function to a single line ending in { // TODO } }.)
This function will break some HTML constructs as well. In particular, it will destroy the content of <pre> tags, as it does not recognize that whitespace is significant in that context.
(Both of these caveats are mentioned in highly upvoted comments on the post you got this code from, by the way!)
Related
Functions defined inside an ES6 module embedded in an HTML script are not available to that script. Thus if you have a statement such as:
<button onclick="doSomething();">Do something</button>
in your HTML and your doSomething() function lives inside an ES6 module embedded in the HTML script, you will get a "doSomething() is undefined" error when you run the script.
Use functions defined in ES6 module directly in html suggests a great solution to the immediate problem, recommending that you "bind" your function to the button by amending your HTML thus:
<button id="dosomethingbutton">Do something</button>
and using the module itself to create a linkage thus:
document.getElementById('dosomethingbutton').addEventListener('click', doSomething);
This works fine, but what if your original button was a bit more sophisticated and was parameterised? For example:
<button onclick="doSomething('withThisString');">Do Something with String</button>
The most that the "binding" can provide seems to be limited to the circumstances relating to the event - I can find no way of associating it with data. I'm completely stuck trying to find a solution to this one and assistance would be much appreciated.
I'd like to add that, while this problem might seem a bit obscure, I think it will be of interest to anyone migrating to Firebase 9. Amongst other changes, migration requires you to move your javascript code into ES6 modules (where the namespace is not directly available to the HTML DOM) and so it's likely that the simplest HTML will immediately hit these issues. Advice would be most welcome.
This works fine, but what if your original button was a bit more sophisticated and was parameterised?
There are a couple of solutions to that:
A data-* attribute:
<button id="the-button" data-string="withThisString">Do Something with String</button>
document.getElementById("the-button").addEventListener("click", function() {
doSomething(this.getAttribute("data-string"));
});
(More on this below.)
or
Binding the string when you bind the event
<button id="the-button">Do Something with String</button>
document.getElementById("the-button").addEventListener("click", () => {
doSomething("withThisString");
});
There are lots of variations on the above, and if you use doSomething with multiple buttons with different strings you can do #1 with a class and a loop rather than with an id, but that's the general idea.
Re the data-* attribute thing: If you wanted to, you could make this process entirely HTML-driven via data-* attributes and a single function that hooks things up. For instance, say you had these buttons:
<button data-click="doThisx#module1">Do This</button>
<button data-click="doThat#module2">Do That</button>
<button data-click="doTheOther#module3">Do The Other</button>
You could have a single reusable function to hook those up:
class EventSetupError extends Error {
constructor(element, msg) {
if (typeof element === "string") {
[element, msg] = [msg, element];
}
super(msg);
this.element = element;
}
}
export async function setupModuleEventHandlers(eventName) {
try {
const attrName = `data-${eventName}`;
const elements = [...document.querySelectorAll(`[${attrName}]`)];
await Promise.all(elements.map(async element => {
const attrValue = element.getAttribute(`data-${eventName}`);
const [fname, modname] = attrValue ? attrValue.split("#", 2) : [];
if (!fname || !modname) {
throw new EventSetupError(
element,
`Invalid '${attrName}' attribute "${attrValue}"`
);
}
// It's fine if we do import() more than once for the same module,
// the module loader will return the same module
const module = await import(`./${modname}.js`);
const fn = module[fname];
if (typeof fn !== "function") {
throw new EventSetupError(
element,
`Invalid '${attrName}': no '${fname}' on module '${modname}' or it isn't a function`
);
}
element.addEventListener(eventName, fn);
}));
} catch (error) {
console.error(error.message, error.element);
}
}
Using it to find and hook up click handlers:
import { setupModuleEventHandlers } from "./data-attr-event-setup.js";
setupModuleEventHandlers("click")
.catch(error => {
console.error(error.message, error.element);
});
It's one-time plumbing but gives you the same attribute-based experience in the HTML (the event handlers could still get parameter information from another data-* attribute, or you could bake that into your setup function). (That example relies on dynamic import, but that's supported by recent versions of all major browsers and, to varying degrees, bundlers.
There are a couple of dozen ways to spin that and I'm not promoting it, just giving an example of the kind of thing you can readily do if you want.
But really, this is where libraries like React, Vue, Ember, Angular, Lit, etc. come into play.
Although T.J Crowder has already answered this question I thought I might add a few points that are difficult to squeeze in as comments.
Once I got further into my Firebase V9 conversion I began to find that some of the consequences of the "module namespacing" issue were quite profound. The example cited in my initial question is easily dealt with, as above, but I found that I also needed to work out what to do about "dynamic" HTML responding to variable circumstances derived from a database. In this case, where my javascript would originally have created a string containing a block of HTML such as:
realDiv = `
<div>
<button onclick = "function fn1 (param1a, param1b,...);">button1</button>
<button onclick = "function fn2 (param2a, param2b,...);">button2</button>
etc
</div>
`
and then thrown this into a "real" realdiv defined in the HTML skeleton of the application with a
document.getElementById("realdiv") = realDiv;
Now, for the reasons described above, once the javascript is in a module, this arrangement no longer works.
The pattern I learnt to adopt (thanks, once again to T.J Crowder) went along the following lines:
realDiv = `
<div>
<button id = "button1" data-param1a="param1a" data-param1b="param1b";">button1</button>
<button id = "button2" data-param2a="param2a" data-param2b="param2b";">button2</button>
etc
</div>
`
I would then throw the generated code into my HTML skeleton as before with
document.getElementById("realdiv") = readlDiv;
and now that you've got the code embedded into the DOM (and assuming that I've kept a count of the number of buttons you've generated) I would create bindings for them with a final bit of javascript like so:
for (let i = 0; i>buttonCount; i++) {
document.getElementById('button' + i).onclick = function() { fn1 (this.getAttribute('data-param1a'), this.getAttribute('data-param1b') };
etc
}
I found that creating onclicks with this pattern was particularly helpful in maintaining clarity when I needed to make the onclick launch a number of different functions.
I have been working on some custom code for a sugar module and am fairly unclear where to put my javascript code to be called in module.
Currently I have put my custom JS in include/javascript/popup_parent_helper.js
This works fine in developer mode but does not work when that is turned off and unfortunately dveloper mode runs SUPER slow
I have done a lot of research and I am getting some conflicting results.
Some tell me that I should include it in:
/modules/[ModuleName]/
Others say that it should to in:
/custom/modules/[ModuleName/
and some further in adding js as a directory
Please help me clarify proper structure for this and where I need to make my proper include statement
Clarifications:
We are using SugarCrm 6.5x
In this case the JS is only being used for one module.
It is being used in the Quick Create View and the Edit View
If the javascript should be accessible by any module, you can create a new JSGrouping and pull in your custom js file using the following technique:
http://support.sugarcrm.com/Documentation/Sugar_Developer/Sugar_Developer_Guide_7.6/Extension_Framework/JSGroupings/#Creating_New_JSGroupings
It sounds like you want it to be isolated to your custom module, so you should probably extend the view desired. If you are extending the record view, create a new file called record.js at custom/modules/-your_module-/clients/base/views/record/
({
extendsFrom: 'RecordView',
initialize: function(options) {
this._super('initialize', [options]);
this.doSomething();
},
doSomething: function(){
console.log("Help you I will");
},
...
})
https://developer.sugarcrm.com/2014/02/10/extending-sugar-7-record-view/
I had also faced similar issue (JS should be work for edit and quickcreate form) but after making some RnD I achieved it as per following way:
\custom\modules\<modulename>\views\view.edit.php
<?php
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
require_once('include/MVC/View/views/view.edit.php');
class {moduleName}ViewEdit extends ViewEdit {
public function __construct() {
parent::ViewEdit();
$this->useForSubpanel = true; // this variable specifies that these changes should work for subpanel
// / $this->useModuleQuickCreateTemplate = true; // quick create template too
}
function display(){ ?>
<?php
$jsscript = <<<EOQ
<script>
Your JS code
</script>
EOQ;
parent::display();
echo $jsscript; //echo the script
}
}
?>
So as you might know, Razor Syntax in ASP.NET MVC does not work in external JavaScript files.
My current solution is to put the Razor Syntax in a a global variable and set the value of that variable from the mvc view that is making use of that .js file.
JavaScript file:
function myFunc() {
alert(myValue);
}
MVC View file:
<script language="text/javascript">
myValue = #myValueFromModel;
</script>
I want to know how I can pass myValue directly as a parameter to the function ? I prefer to have explicit calling with param than relying on globals, however I'm not so keen on javascript.
How would I implement this with javascript parameters? Thanks!
Just have your function accept an argument and use that in the alert (or wherever).
external.js
function myFunc(value) {
alert(value);
}
someview.cshtml
<script>
myFunc(#myValueFromModel);
</script>
One thing to keep in mind though, is that if myValueFromModel is a string then it is going to come through as myFunc(hello) so you need to wrap that in quotes so it becomes myFunc('hello') like this
myFunc('#(myValueFromModel)');
Note the extra () used with razor. This helps the engine distinguish where the break between the razor code is so nothing odd happens. It can be useful when there are nested ( or " around.
edit
If this is going to be done multiple times, then some changes may need to take place in the JavaScript end of things. Mainly that the shown example doesn't properly depict the scenario. It will need to be modified. You may want to use a simple structure like this.
jsFiddle Demo
external.js
var myFunc= new function(){
var func = this,
myFunc = function(){
alert(func.value);
};
myFunc.set = function(value){
func.value = value;
}
return myFunc;
};
someview.cshtml
<script>
myFunc.set('#(myValueFromModel)');
myFunc();//can be called repeatedly now
</script>
I often find that JavaScript in the browser is typically conceptually tied to a specific element. If that's the case for you, you may want to associate the value with that element in your Razor code, and then use JavaScript to extract that value and use it in some way.
For example:
<div class="my-class" data-func-arg="#myValueFromModel"></div>
Static JavaScript:
$(function() {
$('.my-class').click(function() {
var arg = $(this).data('func-arg');
myFunc(arg);
});
});
Do you want to execute your function immediately? Or want to call the funcion with the parameter?
You could add a wrapper function with no parameter and inside call your function with the global var as a parameter. And when you need to call myFunc() you call it trough myFuncWrapper();
function myFuncWrapper(){
myFunc(myValue);
}
function myFunc(myParam){
//function code here;
}
I'm trying to learn basic javascript.
I've created this jsfiddle:
http://jsfiddle.net/Friar_Broccoli/b2gur/
function useless(callback) { return callback(); }
var text = 'Domo arigato!';
assert(useless(function(){ return text; }) === text,
"The useless function works! " + text);
which is straight out of page 37 of:
http://netcraft.co.il/fedia/books/SecretsoftheJavaScriptNinja.pdf
It does NOTHING, and if I add:
document.writeln(text);
it works only if I place it immediately after the "var text = .." declaration. Not the first time I've had this type of problem, although I sometimes succeed in getting javascript functions to work properly.
So
(1) Why does nothing work after the assert() call?
(2) How can I make it work?
(3) Is there somewhere I can find a for-morons explanation of how to organize code in a *.js file?
Thanks;
1) There is no assert() function, so the code fails and doesn't do the rest. If you put an output right after " var text = 'Domo arigato!'; ", it works only because it simply manages to get up to that point without an error.
2) You need to define your own assert function, something like :
function assert(condition,okMessage,failMessage){
if(condition) document.writeln(okMessage);
else document.writeln(failMessage);
}
3) There is no problem with your code organization.
Your fiddle has no defined "assert" method.
This is a good read for you :
http://www.w3schools.com/js/js_functions.asp
functions don't start themself, you need to start them with an onload / onclick / etc event.
Someone on the RubyRogues podcast once said "Learn CoffeeScript because CoffeeScript writes better javascript than you do." Sorry, can't remember who said it...
So, I took a very simple WORKING javascript function:
togglingii.js
function pdtogglejs(id) { $('div .partybackground').removeClass("hidden"); }
Which is being called by this line:
Read More...
Then I converted it into this coffeescript:
toggling.js.coffee
pdtogglecs(id) ->
jQuery('div .partybackground').removeClass("hidden")
and changed the html to reference the pdtoggle*c*s instead of pdtoggle*j*s.
I can see BOTH of them just fine in my application.js file:
(function() {
pdtogglecs(id)(function() {
return jQuery('div .partybackground').removeClass("hidden");
});
}).call(this);
function pdtogglejs(id) { $('div .partybackground').removeClass("hidden"); }
;
(function() {
}).call(this);
However, only the pure javascript works. The coffeescript always returns Uncaught ReferenceError: pdtogglecs is not defined.
Based on other stackoverflow questions it must be some sort of namespace error. Probably because of the way pdtogglecs is, itself, inside of a function?? However, I have tried defining the coffeescript function with: window.pdtogglecs, this.pdtogglecs, root.pdtogglecs and the coffescript one always fails with that error.
What am I missing??
Thanks!!
You have two problems, one is a bit of CoffeeScript syntax confusion and the other is the namespace problem that you know about.
We'll start by sorting out your syntax confusion. This:
f(x) -> ...
is interpreted like this:
f(x)(-> ...)
So when given this:
pdtogglecs(id) ->
jQuery('div .partybackground').removeClass("hidden")
CoffeeScript thinks you're trying to call pdtogglecs as a function with id as an argument. Then it thinks that pdtogglecs(id) returns a function and you want to call that function with your -> jQuery(...) function as an argument. So it ends up sort of like this:
callback = -> jQuery(...)
returned_function = pdtogglecs(id)
returned_function(callback)
And that's nothing like your original JavaScript. You want to create a function named pdtogglecs which takes id as an argument and then runs your jQuery stuff:
pdtogglecs = (id) ->
# -----^ this is sort of important
jQuery('div .partybackground').removeClass("hidden")
You can see what's going on by looking at the generated JavaScript.
The namespace problem is easy and you can probably figure that out based on the other question you found. However, I'll take care of it right here for completeness.
CoffeeScript wraps each .coffee file in a self-executing function to avoid polluting the global namespace:
(function() {
// JavaScript version of your CoffeeScript goes here...
})();
That wrapper makes everything scoped to the .coffee file. If you want to pollute the global namespace then you have to say so:
window.pdtogglecs = (id) -> ...
You can also say:
#pdtogglecs = (id) -> ...
but I prefer the explicitness of directly referencing window, that also saves you from worrying about what # (AKA this) is when you're code is parsed.