Override DateTimeShortcut in Django Admin - javascript

I'm trying to implement the top solution here, the one that only uses javascript:
Django: how to change the choices of AdminTimeWidget
It basically uses regex to create the different time choices by overriding the time options.
The only problem I have is that my script loads before DateTimeShortcuts.js, so I get an Uncaught ReferenceError: DateTimeShortcuts is not defined. Does anyone know how I can force the DateTimeShortcuts.js file to load after my js file that references it?
If I create a second reference to DateTimeShortcuts.js, it'll work properly, but I'll have two clocks up there, only the 2nd one will modify, because it's loading after the 2nd DateTimeShortcuts.js
I'm calling my file like this, where admin_clock.js references DateTimeShortcuts.js and has the override code:
class EventAdmin(admin.ModelAdmin):
list_filter = ('film', 'partner',)
list_display = ('id', partner', 'film', 'date_time', 'venue_name', 'city')
class Media:
js = ('tiny_mce/tiny_mce.js', 'tiny_mce/textareas.js', 'admin_clock.js',)
Apologies for not commenting on the original answer, I need more points to comment there.

The way I solved this was to add the javascript directly to the extrahead block of my version of the change_form template, overriding that block in the template: https://docs.djangoproject.com/en/1.6/ref/contrib/admin/#overriding-admin-templates

Related

userFlow with AngularJS?

I can't get UserFlow to work for our AngularJS app.
The product runs on old AngularJS (1.8) and we love the concept of UserFlow , but the typical injection and init model runs in the core JS scope which AngularJS does not have access to... so, even after following the onboarding instructions, every user that registers is appearing to UserFlow as the same {{userId}}
We believe this is happening (UserFlow is not able to receive the user ID in userflow.identify as described here) because the user ID is not known outside of the AngularJS digest. i.e. - the method was called in a place where angularJS is not taking effect, so the handlebars never get rewritten.
Got it fixed. An overview of how we fixed it is below:
Simply split UserFlow's initialization into two distinct steps:
userflow.init() - this can be directly in your index.html or otherwise injected into the <body>
userflow.identify() - this has to be done within your AngularJS controller
.
DETAILED STEPS---------------
1. init() normally, but use a build variable and don't identify yet
In index.html, at the bottom of the <body> tag, add the following script:
<!-- UserFlow -->
<script ng-if="customization.features.userflow">
!function(){var e="undefined"==typeof window?{}:window,t=e.userflow;if(!t){var r="https://js.userflow.com/";t=e.userflow={_stubbed:!0};var n=e.USERFLOWJS_QUEUE=e.USERFLOWJS_QUEUE||[],o=function(e){t[e]=function(){var t=Array.prototype.slice.call(arguments);i(),n.push([e,null,t])}},s=function(e){t[e]=function(){var t,r=Array.prototype.slice.call(arguments);i();var o=new Promise((function(e,r){t={resolve:e,reject:r}}));return n.push([e,t,r]),o}},a=function(e,r){t[e]=function(){return r}},u=!1,i=function(){if(!u){u=!0;var t=document.createElement("script");t.async=!0;var n=e.USERFLOWJS_ENV_VARS||{};"es2020"===(n.USERFLOWJS_BROWSER_TARGET||function(e){for(var t=[[/Edg\//,/Edg\/(\d+)/,80],[/OPR\//,/OPR\/(\d+)/,67],[/Chrome\//,/Chrome\/(\d+)/,80],[/Safari\//,/Version\/(\d+)/,14],[/Firefox\//,/Firefox\/(\d+)/,74]],r=0;r<t.length;r++){var n=t[r],o=n[0],s=n[1],a=n[2];if(e.match(o)){var u=e.match(new RegExp(s));if(u&&parseInt(u[1],10)>=a)return"es2020";break}}return"legacy"}(navigator.userAgent))?(t.type="module",t.src=n.USERFLOWJS_ES2020_URL||r+"es2020/userflow.js"):t.src=n.USERFLOWJS_LEGACY_URL||r+"legacy/userflow.js",t.onerror=function(){u=!1,console.error("Could not load Userflow.js")},document.head.appendChild(t)}};o("_setTargetEnv"),o("closeResourceCenter"),o("init"),o("off"),o("on"),o("prepareAudio"),o("registerCustomInput"),o("remount"),o("reset"),o("setCustomInputSelector"),o("setCustomNavigate"),o("setCustomScrollIntoView"),o("setInferenceAttributeFilter"),o("setInferenceAttributeNames"),o("setInferenceClassNameFilter"),o("setResourceCenterLauncherHidden"),o("setScrollPadding"),o("setShadowDomEnabled"),o("setPageTrackingDisabled"),o("setUrlFilter"),o("openResourceCenter"),o("toggleResourceCenter"),s("endAll"),s("endAllFlows"),s("endChecklist"),s("group"),s("identify"),s("identifyAnonymous"),s("start"),s("startFlow"),s("startWalk"),s("track"),s("updateGroup"),s("updateUser"),a("getResourceCenterState",null),a("isIdentified",!1)}}();
userflow.init('##grunt_userflow')
</script>
Since we use Grunt as a build tool (which I don't recommend, but you can replicate the same pattern with different technologies), we put the environment-specific token, ##grunt_userflow, into our build script which replaces the individual token to match the respective environment.
You'll notice here we're not calling userflow.identify() yet...
2. Execute the UserFlow identify() directly within the controller
When the user first logs in, now you need to execute the userflow.identify() function and pass in the right IDs. Personally, I like putting AngularJS-agnostic functions like this outside of the controller and then inherit them in:
const startUserFlow = function(userId, login) {
userflow.identify(userId, {
email: login
});
};
And, now calling that function from within AJS:
$scope.processCredentials($scope.username, response.data.access_token).then(function (result) {
trackEvent('signIn', $rootScope.userProfile.id);
startUserFlow($rootScope.userProfile.id, $scope.username);
3. Finally, to reinitialize your Content, use ng-click=() on any HTML you'd like
That's right - since we're scoping it in and doing this the AngularJS way, use ng-click like any other function and bind it directly. Example below.
$scope.launchUserFlowChecklist = function () {
userflow.start('[insert content ID here]');
};
I hope this helps! Cheers.

How to deal with DOM elements?

I am learning about writing custom JavaScript for my Odoo 10 addons.
I've written the following piece of code:
odoo.define('ioio.io', function(require) {
'use strict'
const e = $('div.o_sub_menu_footer')
console.log('--testing--'.repeat(7))
console.log(e)
// the "Powered by Odoo" down the secondary menu
e.remove()
})
The code is well loaded and I can see my testing string in the console.
However when this code is being loaded before the target div, so e empty/not yet filled and thus its content is not removed.
Doing it manually from the console works.
My question is what is the right way to do that? And how to know exactly when the code gets executed?
You can
put your html code before the script tag in your file
use jQuery $(document).ready(...);
Place your script at the bottom of the <body> tag to make sure the DOM renders before trying to manipulate it.
This is an Odoo specific question, so you should use the Odoo standard way, which is via its base JS class. That class contains a ready() method which does exactly what you need.
In your case, to use that function, you need to require the class first. Then you can use ready().
Updating your code, it should look like this:
odoo.define('ioio.io', function(require) {
'use strict'
// require base class
var base = require('web_editor.base');
//use its ready method
base.ready().done(function () {
// put all the code you want to get loaded
// once the DOM is loaded within this block
const e = $('div.o_sub_menu_footer')
console.log('--testing--'.repeat(7))
console.log(e)
// the "Powered by Odoo" down the secondary menu
e.remove()
});
})
While your accepted answer leads to the same outcome, you might want to update it to this one since this is the Odoo way. It's generally advised to work within the Odoo framework as much as possible and customise only if really needed. (Though it can be tough to learn what features Odoo already provides because of its poor documentation.)

How to Add Global/Public Variables to grunt-contrib-uglify Output

Okay, so I am way new to Grunt and Node.js. I am building a site, and decided that the 'main.js' file was getting way too big. So, I split it up, and I am now trying to use Grunt to piece all of these JS files back together.
The issue that I have is that I need to make some global variables available to all of the various functions in all of these JS files. To be more specific, every page on our site is identified via an id in the body tag:
<body id="home">
Many of these JS files contain if statements that ensure certain functions only run if the appropriate page is loaded. For example:
if (page == 'home') {
var title = "Home Page"
$('.page-title').text(title);
}
Notice the page variable? That guy is the one that I need to make available to all of these files (after grunt-contrib-uglify merges them together). So, I figured I'd assign a new "unique" variable name, and make it global.
I noticed that grunt-contrib-uglify has a 'wrap' option listed in its documentation. However, no examples are given as to how to use it.
Can anyone tell me:
- How to use the 'wrap' option in 'grunt-contrib-uglify'
- If this is the right grunt plugin for what I am trying to do?
One idea I had (as a last resort) is to create a before.js and after.js and put the beginning and end (respectively) of what I wish to wrap around the other files in each. But, I think the 'wrap' option is what I need, yes?
UPDATE: Here is a link to my "merged" JS file:
main.js
And a link to my Gruntfile:
Gruntfile.js
I have been having the same problem an searching for a solution. But I think I found an answer.
Use this in your gruntfile:
uglify: {
options: {
wrap: true
}
}
The documentation for the wrap property indicates that the variables will be made available in a global variable, and looking at the generated code that does seem to to be the case. Passing a string value to the parameter does seem to create a global variable with that name.
However, wrap: true seems to make all objects and properties available in the global scope. So instead of globals.page.title (which I can't get to work, anyway), you can just use page.title. Much, much easier and simpler.
If this suits your purposes, I'd recommend doing this instead.
Ok this one is tricky, I have been stucked for a while...
Way you do this with grunt-contrib-uglify for frontend JS
create multiple files like
SomeClass.js
OtherClass.js
main.js
and use some module (grunt-file-dependencies or grunt-contrib-concat) and setup it to concat your files. Then setup uglify in your Gruntfile.js like
...
uglify: {
options: {
wrap: "myPublicObject",
...
},
In file (main.js for example) exports variable has to be assigned, the entire file might look like this:
var otherClassInst = new OtherClass();
var someClassInst = new SomeClass();
exports = otherClassInst;
Now what it exactly does
Uglify will pick superior context (this) and define property on it named myPublicObject. Then it wrap your sourcecode with function and create exports variable here (DO NOT DECLARE var exports anywhere). At the end it will assign what you exported to that property. If you dont assign anything (you dont use exports =) inital value is {}, so the property with void object exists anyway.
To make this super-clear,
if you put your code into page like <script src="myminifiedfile.min.js"></script>, then superior context is window =>
window.myPublicObject is instance of OtherClass while window.someClassInst, window.SomeClass and window.OtherClass are undefined.
this is unlikely, but if you just copy content of minified result and wrap it with different function, object you exported will be visible only via this["myPublicObject"] => uglify wrap doesn't make things globaly accessible, it makes them accessible in superior context.

How does jQuery solve <script defer> issue?

So, I have three js files. All three files are attached to html page without defer:
1)jquery
2)file with the following content
ugu={
temp:function(s){
alert(s);
}
};
3)file with the following content
$.ajax(....) //line1
ugu.temp("hello");//line2
So we see, that third file uses objects from 1 and 2. It doesn't have problems with line1. However line2 has errors if I attach 3 js script to html page without "defer", otherwise it throws error that the browser can't find variable ugu.
The question - how can I make work file 3 without "defer"?
What is the order of including your files? In HTML file you should first include jQuery, then file with ugu definition, and the third one at last.
If you are trying to declare ugu in the global namespace you should use var.
var ugu={
temp:function(s){
alert(s);
}
};
File 2 will have to be loaded before file 3 will run. You might consider looking into require.js it is handy for this.

error with google swiffy calling runtime.js multiple times in the same page

I have converted multiple swf files using google swiffy v5.2 and will have my new animations displayed on many different pages, most of which I do not have control of or access to. In order for the animation to work it needs the swiffy's runtime.js file, which might look something like this on the page:
<script src="https://www.gstatic.com/swiffy/v5.2/runtime.js"></script>
The problem arises when I either have multiple instances of the animation on the same page or a client has this runtime.js file included on their own. When checking the javascript console I get this error:
Uncaught TypeError: Cannot redefine property: __swiffy_override - runtime.js:186
If i was only worried about the conflict with myself I could possibly keep track of a variable or check if the script src existed already, however I do not have this luxury when a client's page may have renamed or changed the source to this file.
Is there a way to prevent the swiffy runtime.js from redefining this property when there are multiple instances of the same javascript file being included on the page?
I imagine you are seeing this problem happen when using AS3 swfs, which have Document classes applied to them. For example, say you have animationAS3.swf, which uses AnimationBaseClass.as. When it is "compiled" by Google Swiffy service the resultant JSON data will contain
{"internedStrings":["...", "AnimationBaseClass", "..."] ....}
The Google Swiffy runtime applies JavaScript's defineProperties() or perhaps defineProperty() to seal an "AnimationBaseClass" object it creates. So, when another instance of the data is loaded the Swiffy runtime attempts to do the same thing again, and the JavaScript interpreter says "Hey, I've already defined that object, I won't redefine it."
The solution I've found, which I believe is inefficient, is to rename the class before giving the data to the Swiffy runtime. Like this:
var classEnumerator = 0;
$.getJSON('animationAS3.json', function(data) {
// Due to "TypeError: Cannot redefine property: AnimationBaseClass",
// we need to enumerate the name of the class. I have no idea about
// the impact on resource usage when doing this.
var classNameIndex;
var i = data.internedStrings.length;
while(i--) {
if (data.internedStrings[i].indexOf("AnimationBaseClass") > -1) {
classNameIndex = i;
}
}
data.internedStrings[classNameIndex] = "AnimationBaseClass_" + (classEnumerator++));
}

Categories

Resources