├── .npmignore ├── .eslintignore ├── .gitignore ├── .eslintrc ├── .travis.yml ├── .babelrc ├── test ├── .eslintrc ├── mocha.opts ├── helpers │ ├── setup.js │ └── render.js └── SoundCloud-test.js ├── example ├── webpack.config.js ├── components │ ├── OptionsInput.js │ ├── CustomWidget.js │ └── OptionsTable.js ├── index.html └── index.js ├── src ├── lib │ └── createWidget.js └── SoundCloud.js ├── LICENSE ├── package.json └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | dist/__tests__ 2 | src 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | dist 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-airbnb" 3 | } 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.2.1" 4 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | test/*-test.js 2 | --compilers js:babel-core/register 3 | --require ./test/helpers/setup.js 4 | -------------------------------------------------------------------------------- /test/helpers/setup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | 5 | import { jsdom } from 'jsdom'; 6 | 7 | global.document = jsdom(''); 8 | global.window = document.defaultView; 9 | global.navigator = global.window.navigator; 10 | -------------------------------------------------------------------------------- /example/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: `${__dirname}/index.js`, 3 | output: { 4 | path: __dirname, 5 | filename: 'bundle.js', 6 | publicPath: '/', 7 | }, 8 | 9 | module: { 10 | loaders: [ 11 | { test: /\.js$/, exclude: /node_modules/, loader: 'babel'}, 12 | ], 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /src/lib/createWidget.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | 5 | import load from 'load-script'; 6 | 7 | /** 8 | * Create a new widget by requesting and using the SoundCloud Widget API. 9 | * 10 | * @param {String} id - reference to iframe element for widget 11 | * @param {Function} cb 12 | */ 13 | 14 | const createWidget = (id, cb) => { 15 | // load the API, it's namespaced as `window.SC` 16 | return load('https://w.soundcloud.com/player/api.js', () => { 17 | return cb(window.SC.Widget(id)); // eslint-disable-line new-cap 18 | }); 19 | }; 20 | 21 | /** 22 | * Expose `createWidget` 23 | */ 24 | 25 | export default createWidget; 26 | -------------------------------------------------------------------------------- /example/components/OptionsInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | TextField, 4 | } from 'material-ui'; 5 | 6 | export default class OptionsInput extends React.Component { 7 | static propTypes = { 8 | default: React.PropTypes.string.isRequired, 9 | type: React.PropTypes.string.isRequired, 10 | onChange: React.PropTypes.func.isRequired, 11 | }; 12 | 13 | onSubmit(event) { 14 | event.preventDefault(); 15 | this.props.onChange(this.refs.input.getValue()); 16 | } 17 | 18 | render() { 19 | return ( 20 |
21 | 28 | 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example/components/CustomWidget.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import SoundCloud from '../../'; 3 | 4 | export default class CustomWidget extends React.Component { 5 | static propTypes = { 6 | url: React.PropTypes.string.isRequired, 7 | id: React.PropTypes.string.isRequired, 8 | opts: React.PropTypes.array.isRequired, 9 | }; 10 | 11 | componentDidUpdate() { 12 | // otherwise it would only update when `url` changes. 13 | this.refs.widget.forceUpdate(); 14 | } 15 | 16 | render() { 17 | const opts = this.props.opts.reduce((all, param) => { 18 | return { 19 | ...all, 20 | [param.name]: param.toggled, 21 | }; 22 | }, {}); 23 | 24 | return ( 25 | 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 troy betz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | react-soundcloud-widget 7 | 8 | 9 | 47 | 48 | 49 |
50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /example/components/OptionsTable.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Table, 4 | TableHeader, 5 | TableHeaderColumn, 6 | TableBody, 7 | TableRow, 8 | TableRowColumn, 9 | } from 'material-ui'; 10 | 11 | export default class OptionsTable extends React.Component { 12 | static propTypes = { 13 | opts: React.PropTypes.array.isRequired, 14 | onChange: React.PropTypes.func.isRequired, 15 | }; 16 | 17 | onRowSelection(selectedIndices) { 18 | const selectedOpts = this.props.opts.map((opt, idx) => ({ 19 | ...opt, 20 | toggled: selectedIndices.indexOf(idx) > -1 ? true : false, 21 | })); 22 | 23 | this.props.onChange(selectedOpts); 24 | } 25 | 26 | render() { 27 | return ( 28 | 31 | 32 | 33 | Parameter 34 | Purpose 35 | 36 | 37 | 38 | { 39 | this.props.opts.map(opt => 40 | 41 | {opt.name} 42 | {opt.purpose} 43 | 44 | ) 45 | } 46 | 47 |
48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-soundcloud-widget", 3 | "version": "2.0.4", 4 | "description": "react.js powered SoundCloud player component", 5 | "main": "dist/SoundCloud.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/troybetz/react-soundcloud-widget.git" 9 | }, 10 | "keywords": [ 11 | "soundcloud", 12 | "player", 13 | "widget", 14 | "react", 15 | "react-component" 16 | ], 17 | "author": "troy betz", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/troybetz/react-soundcloud-widget/issues" 21 | }, 22 | "homepage": "https://github.com/troybetz/react-soundcloud-widget", 23 | "dependencies": { 24 | "load-script": "^1.0.0" 25 | }, 26 | "devDependencies": { 27 | "babel-cli": "^6.2.0", 28 | "babel-core": "^6.2.1", 29 | "babel-eslint": "^4.1.3", 30 | "babel-loader": "^6.2.0", 31 | "babel-preset-es2015": "^6.1.18", 32 | "babel-preset-react": "^6.1.18", 33 | "babel-preset-stage-0": "^6.1.18", 34 | "eslint": "^1.7.3", 35 | "eslint-config-airbnb": "^0.1.0", 36 | "eslint-plugin-react": "^3.6.3", 37 | "expect": "^1.12.2", 38 | "jsdom": "^7.0.2", 39 | "material-ui": "^0.13.3", 40 | "mocha": "^2.3.3", 41 | "proxyquire": "^1.7.3", 42 | "react": "^0.14.0", 43 | "react-addons-test-utils": "^0.14.0", 44 | "react-dom": "^0.14.0", 45 | "react-tap-event-plugin": "^0.2.1", 46 | "webpack": "^1.12.8" 47 | }, 48 | "peerDependencies": { 49 | "react": ">=0.13.0" 50 | }, 51 | "scripts": { 52 | "test": "mocha", 53 | "example": "webpack --config example/webpack.config.js", 54 | "compile": "babel src --out-dir dist", 55 | "prepublish": "npm run compile", 56 | "lint": "eslint ." 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test/helpers/render.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | 5 | import expect from 'expect'; 6 | import proxyquire from 'proxyquire'; 7 | import React from 'react'; 8 | import ReactDOM from 'react-dom'; 9 | import TestUtils from 'react-addons-test-utils'; 10 | 11 | /** 12 | * Stub out SoundCloud 13 | */ 14 | 15 | function setup() { 16 | const widgetStub = { 17 | load: expect.createSpy(), 18 | bind: expect.createSpy(), 19 | unbind: expect.createSpy(), 20 | }; 21 | 22 | const SoundCloud = proxyquire('../../src/SoundCloud', { 23 | './lib/createWidget': { 24 | default: (id, cb) => cb(widgetStub), 25 | }, 26 | }).default; 27 | 28 | return { 29 | widgetStub, 30 | SoundCloud, 31 | }; 32 | } 33 | 34 | /** 35 | * Shallow rendering 36 | */ 37 | 38 | export function render(props) { 39 | const { SoundCloud } = setup(); 40 | 41 | const renderer = TestUtils.createRenderer(); 42 | renderer.render(React.createElement(SoundCloud, props)); 43 | 44 | const output = renderer.getRenderOutput(); 45 | 46 | function rerender(newProps = {}) { 47 | renderer.render(React.createElement(SoundCloud, { 48 | ...props, 49 | ...newProps, 50 | })); 51 | 52 | return renderer.getRenderOutput(); 53 | } 54 | 55 | return { 56 | props, 57 | output, 58 | rerender, 59 | }; 60 | } 61 | 62 | /** 63 | * Full rendering into the dom 64 | */ 65 | 66 | export function renderDOM(props) { 67 | const { widgetStub, SoundCloud } = setup(); 68 | 69 | /** 70 | * Emulate changes to component.props using a container component's state 71 | */ 72 | 73 | class Container extends React.Component { 74 | constructor(_props) { 75 | super(_props); 76 | 77 | this.state = _props; 78 | } 79 | 80 | render() { 81 | return ; 82 | } 83 | } 84 | 85 | const div = document.createElement('div'); 86 | const container = ReactDOM.render(, div); 87 | const output = TestUtils.findRenderedComponentWithType(container, SoundCloud); 88 | 89 | function rerender(newProps = {}) { 90 | container.setState(newProps); 91 | return output; 92 | } 93 | 94 | function unmount() { 95 | ReactDOM.unmountComponentAtNode(div); 96 | } 97 | 98 | return { 99 | props, 100 | output, 101 | rerender, 102 | unmount, 103 | widgetStub, 104 | }; 105 | } 106 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import injectTapEventPlugin from 'react-tap-event-plugin'; 4 | import CustomWidget from './components/CustomWidget'; 5 | import OptionsTable from './components/OptionsTable'; 6 | import OptionsInput from './components/OptionsInput'; 7 | 8 | class Example extends React.Component { 9 | constructor(props) { 10 | super(props); 11 | 12 | this.state = { 13 | id: 'soundcloud-id', 14 | url: 'https://soundcloud.com/sylvanesso/coffee', 15 | opts: [ 16 | {name: 'auto_play', purpose: 'Start playing the widget after it’s loaded', toggled: false}, 17 | {name: 'visual', purpose: 'Display widget in visual mode', toggled: true}, 18 | {name: 'buying', purpose: 'Show/hide buy buttons', toggled: true}, 19 | {name: 'liking', purpose: 'Show/hide like buttons', toggled: true}, 20 | {name: 'download', purpose: 'Show/hide download buttons', toggled: true}, 21 | {name: 'sharing', purpose: 'Show/hide share buttons/dialogues', toggled: true}, 22 | {name: 'show_artwork', purpose: 'Show/hide artwork', toggled: true}, 23 | {name: 'show_comments', purpose: 'Show/hide comments', toggled: true}, 24 | {name: 'show_playcount', purpose: 'Show/hide number of sound plays', toggled: true}, 25 | {name: 'show_user', purpose: 'Show/hide the uploader name', toggled: true}, 26 | {name: 'show_reposts', purpose: 'Show/hide reposts', toggled: false}, 27 | {name: 'hide_related', purpose: 'Show/hide related tracks', toggled: false}, 28 | ], 29 | }; 30 | } 31 | 32 | render() { 33 | return ( 34 |
35 |
36 | 41 |
42 | 43 |
44 | this.setState({ url })} /> 48 | this.setState({ id })} /> 52 | this.setState({ opts })} /> 55 |
56 |
57 | ); 58 | } 59 | } 60 | 61 | injectTapEventPlugin(); 62 | ReactDOM.render(, document.getElementById('react-root')); 63 | -------------------------------------------------------------------------------- /test/SoundCloud-test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import React from 'react'; 3 | import { render, renderDOM } from './helpers/render'; 4 | 5 | window.SC = { 6 | Widget: { 7 | Events: { 8 | PLAY: 'play', 9 | PAUSE: 'pause', 10 | FINISH: 'finish', 11 | }, 12 | }, 13 | }; 14 | 15 | const url = 'https://soundcloud.com/sylvanesso/coffee'; 16 | 17 | describe('SoundCloud Widget', () => { 18 | it('should render an iframe', () => { 19 | const { output } = render({ url }); 20 | expect(output).toEqual( 21 |