Making a HTML source object - javascript

I want to create a little HTML extension which provides a tag (uai) which will be formatted like <uai src="some_what_file.mp3" controller="nav" autoplay="true">
And so on this tag will get a javascript object assigned.
function uai(){
this.src = { ... }
this.controller = { ... }
this.autoplay = { ... }
}
But I wonder how I will apply this function as an object to the html tag and make the HTML tag to apply the source to this.src
This object will be similar to the input tag *
I know the audio tag exists, and I fully know how to use it. But I want to replace the audio tag and functions with this one. It will make it easier for me to make canvas supported audio marks, so that's why I need it.

You can just access it like you would any other element and do what you need to do with it.
console.log(document.querySelector('cookies').getAttribute('flavor'))
<cookies flavor="chocolate chip"></cookies>
There are two important catches you should be aware of though:
First, it can't be self-closing. Browsers handle self-closing elements (<cookies />) in a special way, and you can't create custom self-closing tags (this is also a limitation that frameworks like Angular have to deal with). It has to have a closing tag, even if it has no children: <cookies></cookies>
Second, you can't do things like document.querySelector('cookies').flavor and access the property directly. You need to use document.querySelector('cookies').getAttribute('flavor') or .setAttribute(). You can however apply it yourself to use it latter:
Array.prototype.slice.call(document.querySelectorAll('cookies'), 0).forEach(cookie => Object.defineProperty(cookie, 'flavor', {
get: () => cookie.getAttribute('flavor'),
set: (value) => cookie.setAttribute('flavor', value)
}));
let cookie = document.querySelector('cookies');
console.log(cookie.flavor);
cookie.flavor = 'sugar';
console.log(cookie.flavor);
console.log(cookie);
<cookies flavor="chocolate chip"></cookies>
<cookies flavor="peanut butter"></cookies>

Using a transpiler that support classes and extends is very easy.
class UAIElement extends HTMLElement {
}
document.registerElement('uai', UAIElement);
the plain js version:
document.registerElement('uai', {
prototype: Object.create(HTMLElement.prototype, {
extends: 'audio'
})
});

If you want to use Custom Elements you need to insert an hyphen - in the name of your tag to be sure it won't be used in the future standard, for example: <ultimate-audio>.
Also you should use now the version 1 of the Custom Elements specification, which uses customElements.define() instead of document.registerElement() to register a new element.
Last, you cannot use the extends:'audio' option if you want to create a new tag.
You can use the class definition in all modern browsers:
Content of ulimate-audio.html:
<template>
<h3>UAI</h3>
<nav>
<button id=PlayBtn>Play</button>
<button id=StopBtn>Stop</button>
<input id=AutoplayCB type=checkbox disabled>Auto-Play
<div>Source : <output id=SourceOut></output></div>
</nav>
</template>
<script>
( function ( owner )
{
class UAI extends HTMLElement
{
constructor ()
{
super()
this.model = {}
this.view = {}
}
connectedCallback ()
{
//View
this.innerHTML = owner.querySelector( 'template' ).innerHTML
this.view.autoplay = this.querySelector( '#AutoplayCB' )
this.view.source = this.querySelector( '#SourceOut' )
//Model
//autoplay
var attr = this.getAttribute( 'autoplay' )
this.model.autoplay = typeof attr == 'string' && ( attr == '' || attr == 'true' )
//source
this.model.source = this.getAttribute( 'src' )
//Model -> View
this.view.source.textContent = this.model.source
this.view.autoplay.checked = this.model.autoplay
//[play] event
var self = this
this.querySelector( '#PlayBtn' ).addEventListener( 'click', function ()
{
self.play()
} )
this.querySelector( '#StopBtn' ).addEventListener( 'click', function ()
{
self.stop()
} )
//init
if ( this.model.autoplay )
this.play()
}
play ()
{
this.model.audio = new Audio( this.model.source )
this.model.audio.play()
}
stop ()
{
this.model.audio.pause()
}
set src ( file )
{
console.log( '%s.src=%s', this, file )
this.model.source = file
this.model.audio.src = file
this.view.source.textContent = file
if ( this.model.autoplay )
this.model.audio.play()
}
set autoplay ( value )
{
console.warn( 'autoplay=', value )
this.model.autoplay = ( value === "true" || value === true )
this.view.autoplay.checked = this.model.autoplay
}
}
customElements.define( 'ultimate-audio', UAI )
} )( document.currentScript.ownerDocument )
</script>
The UI of you control is defined in the <template>, where you can add a <style> element.
Then you can use your tag like this:
In the header, include the custom element:
<link rel="import" href="ultimate-audio.html">
In the body, use the new tag:
<ultimate-audio id=UA src="path/file.mp3" autoplay></ultimate-audio>
The methods play(), stop(), and the properties src and autoplay can be invoked form javascript:
UA.src = "path/file2.mp3"
UA.play()

Related

How to use imported javascript library constructor in vue.js component?

I want to reuse a javascript library I did some time ago in a vue.js component.
The js library works like this:
simple reference on the main script with tag
css loading
The library provides a constructor, so what is needed is a element with an ID and to init the component in javascript I only need to:
var divelement = new pCalendar("#divelement", {
... various options
});
I'm trying to create a .vue component that is able to do the same (loading js library, init css, init component with the constructor and options), but I can't figure out what is the right way to do it.
This is what I'm working on, but in this situation I get an error because pCalendar is not recognized as constructor.
<template>
<div id="myelement"></div>
</template>
<script>
import perpetual_calendar from '../../../assets/js/perpetual-calendar/perpetual_calendar.js'
export default {
data () {
return {
myelement: '',
}
},
mounted(){
var myelement = new pCalendar("#myelement",{
... various options
});
} ,
}
</script>
<style lang="css">
#import '../../../assets/js/perpetual-calendar/pcalendar_style.css';
</style>
Edit 1 (answer to #Daniyal Lukmanov question):
perpetual_calendar.js looks like this:
var pCalendar = function (element, options) {
this.options = {};
this.initializeElement(element);
this.initializeOptions(options || {});
this._create();
}
...
pCalendar.prototype.initializeElement = function (element) {
var canCreate = false;
if (typeof HTMLElement !== "undefined" && element instanceof HTMLElement) {
this.element = element;
canCreate = true;
} else if (typeof element === "string") {
this.element = document.querySelector(element);
if (this.element) {
canCreate = true;
} else {
canCreate = false;
}
} else {
canCreate = false;
}
if (canCreate === true) {
if (document.getElementsByName(this.element.id+"_store").length!=0) {
canCreate = false;
}
}
return canCreate;
};
and so on ...
Edit 2: this is the initializeOptions function, that is throwing the TypeError: "this.element is null" error.
pCalendar.prototype.initializeOptions = function (options) {
// begin hardcoded options, don't touch!!!
this.options['objectId'] = this.element.id;
this.options['firstMonth'] = null;
(... various options)
// end hardcoded options
for (var key in this.defaultOptions) {
( ... loop to load options - default one or defined by user in the constructor)
}
};
In you perpetual_calendar.js file, you need to export the pCalendar in order to use it. At the bottom of the perpetual_calendar.js file, add:
export {
pCalendar
};
Now, you should be able to import and use it like so:
import { pCalendar } from './perpetual_calendar.js';
let calendar = new pCalendar({ /* parameters*/ });
EDIT After adding initializeElement method
There are a few things wrong in the code:
It seems that not all code paths of initializeElement set the this.element variable.
document.querySelector will not work in vue. You will need to pass the element via the this.$refs variable:
<template>
<div id="myelement" ref="myelement"></div>
</template>
<script>
import perpetual_calendar from '../../../assets/js/perpetual-calendar/perpetual_calendar.js'
export default {
data () {
return {
myelement: '',
}
},
mounted(){
var myelement = new pCalendar(this.$refs["myelement"], { /* various options */ });
}
}
</script>
<style lang="css">
#import '../../../assets/js/perpetual-calendar/pcalendar_style.css';
</style>
Now, you can pass the element to your perpetual_calendar as directly as an object instead of having to use document.querySelector:
pCalendar.prototype.initializeElement = function (element) {
this.element = element.
return true;
};

Capturing images from HTML page in array using javascript

I'm trying to capture all images in an HTML page using Javascript. I found this similar question, the best answer to which gives in excellent detail a solution to the problem:
Detect all images with Javascript in an html page
My question is that I can't get the code shown in the best answer to run without error - even when I copy/paste directly into the Firefox console. I suspect the answer is straightforward, though it's had me scratching my head for hours - is anyone able to help please?
This code gives me the error "SyntaxError: missing ) after argument list"...
var elements = document.body.getElementsByTagName("*");
Array.prototype.forEach.call( elements, function ( el ) {
var style = window.getComputedStyle( el, false );
if ( style.backgroundImage != "none" ) {
images.push( style.backgroundImage.slice( 4, -1 ).replace(/['"]/g, "")
}
}
The full solution, which seems to include the above, also appears to give the same error...
var images = [],
bg_images = [],
image_parents = [];
document.addEventListener('DOMContentLoaded', function () {
var body = document.body;
var elements = document.body.getElementsByTagName("*");
/* When the DOM is ready find all the images and background images
initially loaded */
Array.prototype.forEach.call( elements, function ( el ) {
var style = window.getComputedStyle( el, false );
if ( el.tagName === "IMG" ) {
images.push( el.src ); // save image src
image_parents.push( el.parentNode ); // save image parent
} else if ( style.backgroundImage != "none" ) {
bg_images.push( style.backgroundImage.slice( 4, -1 ).replace(/['"]/g, "") // save background image url
}
}
/* MutationObserver callback to add images when the body changes */
var callback = function( mutationsList, observer ){
for( var mutation of mutationsList ) {
if ( mutation.type == 'childList' ) {
Array.prototype.forEach.call( mutation.target.children, function ( child ) {
var style = child.currentStyle || window.getComputedStyle(child, false);
if ( child.tagName === "IMG" ) {
images.push( child.src ); // save image src
image_parents.push( child.parentNode ); // save image parent
} else if ( style.backgroundImage != "none" ) {
bg_images.push( style.backgroundImage.slice( 4, -1 ).replace(/['"]/g, "") // save background image url
}
} );
}
}
}
var observer = new MutationObserver( callback );
var config = { characterData: true,
attributes: false,
childList: true,
subtree: true };
observer.observe( body, config );
});
Thank you.
You're missing some closing parentheses on the images.push() line and the last line.
Not sure if this will make your code do what you ultimately want it to do, but it will at least not cause a syntax error.
var elements = document.body.getElementsByTagName("*");
Array.prototype.forEach.call( elements, function ( el ) {
var style = window.getComputedStyle( el, false );
if ( style.backgroundImage != "none" ) {
images.push(style.backgroundImage.slice(4, -1).replace(/['"]/g, ""));
}
});

React render component multiple times in container with different props?

I am trying to create a reusable "tag" React component, so that users can create tags onclick and see that information displayed in the DOM.
Here's the module:
module.exports = React.createClass({
render: function() {
return (
<div className="language chip" data-lang={this.props.language} data-lang-level={this.props.level}>
{this.props.language} ({this.props.level})
<i className="material-icons">close</i>
</div>
);
}
});
And the onclick call:
var addLanguage = $('a#add-language');
addLanguage.click(function() {
var languageLearning = $('#language-learning');
var levelLearning = $('#language-level');
if (languageLearning != null && levelLearning != null) {
ReactDOM.render(
<LanguageChip
language={languageLearning.val()}
level={levelLearning.val()}
/>,
document.getElementById('language-chips')
);
languageLearning.select2('val', '');
levelLearning.select2('val', '');
}
})
I didn't realise that when using React.DOM, "Any existing DOM elements inside are replaced when first called." This means when adding a second chip, the first is removed. I want users to be able to have multiple chips.
How can I do this?
I don't know if you've got a good reason to not add the form used to create a tag on your component, but it would be much simpler if you could.
Then you just have to add your tags on an array and display them with your LanguageChip component.
I've made an example here: https://jsfiddle.net/snahedis/69z2wepo/28193/
I don't know what's your level of understanding of React so if something isn't clear let me know :)
Edit: the same example inside a preexistent form:
https://jsfiddle.net/snahedis/69z2wepo/28217/
You need to use array to store multiple chips data. Take a look to this simplified example: http://output.jsbin.com/seficu
var LanguageChips = React.createClass({
render: function() {
return (
<div>
{
(this.props.chipsArray).map(function(chip, index) {
return <LanguageChip
key={index}
language={chip.languageLearning}
level={chip.levelLearning}
/>
})
}
</div>
);
}
});
var LanguageChip = React.createClass({
render: function() {
return (
<div className="language chip" data-lang={this.props.language} data-lang-level={this.props.level}>
{this.props.language} ({this.props.level})
<i className="material-icons"></i>
</div>
);
}
});
var chipsArray = [];
document.getElementById('add-language').addEventListener("click", function() {
var languageLearning = 'test1';
var levelLearning = 'test2';
if (languageLearning != null && levelLearning != null) {
chipsArray.push({
languageLearning: languageLearning,
levelLearning: levelLearning
});
ReactDOM.render(
<LanguageChips chipsArray={chipsArray} />,
document.getElementById('language-chips')
);
}
})

Uploading a pic doesn't just work on android native browser

I've been having problem with this code that uploads an image. It doesn't work on native android browser(Tried on galaxy note 2). It works fine on all other browsers on different machines. Please help if anyone has ever come across this problem before or can figure out what went wrong.
Here is the template -> image_upload_view.hbs
<label>
<div class='label-text'>{{t label }}:</div>
<div class="input-prepend">
<div class="preview" style="background-image: url('{{src}}');"></div>
<i class="icon-arrow-up"></i>
<span class="input-description">{{t description}}</span>
<span class="change-notification hide">{{t 'COMPONENT_IMAGE_UPLOAD_IMAGE_CHANGED'}}</span>
<i class="icons"></i>
<input class="input hide" type="file" name="image_file_name" accept="image/gif, image/jpeg, image/jpg, image/png"></input>
</div>
</label>
Here is the logic -> image_upload_view.js
var Base = require( '../../base' ),
_ = require( 'underscore' ),
oldBrowserPreviewImage = '/images/profiles/edit/default_cover_image_preview.jpg';
module.exports = Base.extend( {
className: 'component-image-upload',
events: {
'change input[type=file]': 'onChange',
'click .remove-image': 'onRemove'
},
getTemplateData: function () {
return _( this._super() ).extend( {
description: this.description,
label: this.label,
src: this.src
} );
},
postRender: function () {
// TODO: Let parent instantiate and pass in models once new hydration logic is merged.
var ModelType = require( '../../../models/image_upload/' + this.type );
this._super();
this.model = new ModelType( {
file_url: this.src
}, {
app: this.app
} );
this.renderImagePreview();
},
onChange: function ( evt ) {
this.updateFile( this.$( evt.currentTarget ) );
},
onRemove: function ( evt ) {
var fileInput = this.$( 'input[type=file]' );
// Prevent remove click from also triggering input's file selection.
this.disableEvent( evt );
// Quirky, but correct way to clear the file input.
// http://stackoverflow.com/questions/1043957/clearing-input-type-file-using-jquery#answer-13351234
fileInput.wrap( '<form>' ).closest( 'form' ).get( 0 ).reset();
fileInput.unwrap();
this.updateFile( fileInput );
},
updateFile: function ( fileInput ) {
this.model.setFile( fileInput );
this.renderImagePreview();
// TODO: Need to let parent know which model type this is until the parent
// can instantiate the model itself (pending hydration).
this.$el.trigger( 'imageUploadComponent:changed', [ this.model, this.type ] );
},
renderImagePreview: function () {
var self = this;
this.model.getFileUrl().done( function ( url ) {
var backgroundImage,
hasFile = self.model.hasFile(),
// If we have a file set but got a null URL back, it means we're using an old browser
// that doesn't support generating local file URLs via the FileReader API.
isOldBrowser = hasFile && url === null;
if ( url ) {
backgroundImage = 'url(\'' + url + '\')';
} else if ( isOldBrowser ) {
// For old browsers, show a generic preview image.
backgroundImage = 'url(\'' + oldBrowserPreviewImage + '\')';
} else {
backgroundImage = 'none';
}
self.$( '.preview' ).css( 'background-image', backgroundImage );
// If we can't show a preview of the selected image (old browsers), display a change notification message
// to let the user know that the selection "took".
self.$( '.change-notification' ).toggleClass( 'hide', !( hasFile && isOldBrowser ) );
self.toggleActionIcon( !url );
} );
},
toggleActionIcon: function ( toggle ) {
var input = this.$( '.icons' );
input.toggleClass( 'icon-add', toggle );
input.toggleClass( 'remove-image icon-close', !toggle );
}
} );
module.exports.id = 'shared/components/image_upload_view';
If you use <file accept="image/*"/> it should also work on older (Android) browser versions.
<file accept="image/jpeg"/> is only supported on newer browsers.
I don't know from which Chrome version it is supported (chrome://version), but:
Chrome v18: doesn't support it.
Chrome v38: supports it.

Programmatic Dijit/Tree Not Appearing in Declarative Dijit/ContentPane

Can anyone help me figure out why this works in Dojo 1.8 but not in 1.9?
In 1.8, the tree is placed within the “pilotTreeContainer” contentpane. In 1.9, the tree is there if you look in Firebug but visually, it just shows a loading graphic. pilotTreeContainer is declared in the template file for the widget that contains this code. All this code is in the postCreate method.
var treeStore = lang.clone( store );
var treeModel = new ObjectStoreModel(
{
store: treeStore,
query: { id: 'all' },
mayHaveChildren: function ( item ) // only ships have the unique attribute
{
return item.unique === undefined;
}
} );
var pilotTree = new Tree(
{
model: treeModel, // do we need to clone?
autoExpand: true,
showRoot: false,
title: 'Click to add',
openOnClick: true,
getDomNodeById: function ( id ) // new function to find DOM node
{
if ( this._itemNodesMap !== undefined && this._itemNodesMap[ id ] !== undefined && this._itemNodesMap[ id ][0] !== undefined ) {
return this._itemNodesMap[ id ][0].domNode;
}
else {
return null;
}
}
} );
this.pilotTree = pilotTree;
this.pilotTreeContainer.set( 'content', pilotTree );
I’ve tried calling startup on both tree and contentpane.
Debugging the dijit/Tree code, it seesm that there is a deferred which never resolves. It is returned from the _expandNode function when called from the _load function (when trying to expand the root node this._expandNode(rn).then).
The part that fails in dijit/Tree is this:
// Load top level children, and if persist==true, all nodes that were previously opened
this._expandNode(rn).then(lang.hitch(this, function(){
// Then, select the nodes specified by params.paths[].
this.rootLoadingIndicator.style.display = "none";
this.expandChildrenDeferred.resolve(true);
}));
Why is the tree not showing? What is going wrong?
Coming back to this (in the hope that it would be solved in Dojo 1.10), I have found a fix.
I abstracted the tree into its own module, adding it to the container with placeAt() instead of using this.pilotTreeContainer.set( 'content', pilotTree );:
// dijit/Tree implementation for pilots
pilotTree = new PilotTree(
{
model: treeModel
} );
// add to container
pilotTree.placeAt( this.pilotTreeContainer.containerNode );
pilotTree.startup();
then forced it to show its content within the tree's startup() method:
startup: function()
{
this.inherited( arguments );
// force show!
this.rootLoadingIndicator.style.display = "none";
this.rootNode.containerNode.style.display = "block";
},

Categories

Resources